From 1af120ea98b63d227fe6915c9bc4491a6896419a Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 12 Nov 2024 01:07:00 -0500 Subject: [PATCH 01/35] Defines and implements the issued asset state types --- zebra-chain/src/orchard/orchard_flavor_ext.rs | 13 +- zebra-chain/src/orchard_zsa.rs | 5 +- zebra-chain/src/orchard_zsa/asset_state.rs | 112 ++++++++++++++++++ zebra-chain/src/orchard_zsa/burn.rs | 39 ++++-- zebra-chain/src/orchard_zsa/issuance.rs | 5 + zebra-chain/src/transaction.rs | 64 ++++++++++ zebra-consensus/src/block.rs | 5 + zebra-state/src/arbitrary.rs | 7 ++ zebra-state/src/request.rs | 27 +++++ zebra-state/src/service/chain_tip.rs | 2 + .../zebra_db/block/tests/vectors.rs | 5 + 11 files changed, 274 insertions(+), 10 deletions(-) create mode 100644 zebra-chain/src/orchard_zsa/asset_state.rs diff --git a/zebra-chain/src/orchard/orchard_flavor_ext.rs b/zebra-chain/src/orchard/orchard_flavor_ext.rs index 6ad05abd889..32d887472f4 100644 --- a/zebra-chain/src/orchard/orchard_flavor_ext.rs +++ b/zebra-chain/src/orchard/orchard_flavor_ext.rs @@ -9,7 +9,10 @@ use proptest_derive::Arbitrary; use orchard::{note_encryption::OrchardDomainCommon, orchard_flavor}; -use crate::serialization::{ZcashDeserialize, ZcashSerialize}; +use crate::{ + orchard_zsa, + serialization::{ZcashDeserialize, ZcashSerialize}, +}; #[cfg(feature = "tx-v6")] use crate::orchard_zsa::{Burn, NoBurn}; @@ -50,7 +53,13 @@ pub trait OrchardFlavorExt: Clone + Debug { /// A type representing a burn field for this protocol version. #[cfg(feature = "tx-v6")] - type BurnType: Clone + Debug + Default + ZcashDeserialize + ZcashSerialize + TestArbitrary; + type BurnType: Clone + + Debug + + Default + + ZcashDeserialize + + ZcashSerialize + + TestArbitrary + + AsRef<[orchard_zsa::BurnItem]>; } /// A structure representing a tag for Orchard protocol variant used for the transaction version `V5`. diff --git a/zebra-chain/src/orchard_zsa.rs b/zebra-chain/src/orchard_zsa.rs index be3e29ec0e4..cbe93771e67 100644 --- a/zebra-chain/src/orchard_zsa.rs +++ b/zebra-chain/src/orchard_zsa.rs @@ -6,8 +6,11 @@ pub(crate) mod arbitrary; mod common; +mod asset_state; mod burn; mod issuance; -pub(crate) use burn::{Burn, NoBurn}; +pub(crate) use burn::{Burn, BurnItem, NoBurn}; pub(crate) use issuance::IssueData; + +pub use asset_state::{AssetBase, AssetState, AssetStateChange, IssuedAssetsChange}; diff --git a/zebra-chain/src/orchard_zsa/asset_state.rs b/zebra-chain/src/orchard_zsa/asset_state.rs new file mode 100644 index 00000000000..bdcba2528fe --- /dev/null +++ b/zebra-chain/src/orchard_zsa/asset_state.rs @@ -0,0 +1,112 @@ +//! Defines and implements the issued asset state types + +use std::{collections::HashMap, sync::Arc}; + +use orchard::issuance::IssueAction; +pub use orchard::note::AssetBase; + +use crate::block::Block; + +use super::BurnItem; + +/// The circulating supply and whether that supply has been finalized. +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct AssetState { + /// Indicates whether the asset is finalized such that no more of it can be issued. + pub is_finalized: bool, + + /// The circulating supply that has been issued for an asset. + pub total_supply: u128, +} + +/// A change to apply to the issued assets map. +// TODO: Reference ZIP +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct AssetStateChange { + /// Whether the asset should be finalized such that no more of it can be issued. + pub is_finalized: bool, + /// The change in supply from newly issued assets or burned assets. + pub supply_change: i128, +} + +impl AssetStateChange { + fn from_note(is_finalized: bool, note: orchard::Note) -> (AssetBase, Self) { + ( + note.asset(), + Self { + is_finalized, + supply_change: note.value().inner().into(), + }, + ) + } + + fn from_notes( + is_finalized: bool, + notes: &[orchard::Note], + ) -> impl Iterator + '_ { + notes + .iter() + .map(move |note| Self::from_note(is_finalized, *note)) + } + + fn from_issue_actions<'a>( + actions: impl Iterator + 'a, + ) -> impl Iterator + 'a { + actions.flat_map(|action| Self::from_notes(action.is_finalized(), action.notes())) + } + + fn from_burn(burn: &BurnItem) -> (AssetBase, Self) { + ( + burn.asset(), + Self { + is_finalized: false, + supply_change: -i128::from(burn.amount()), + }, + ) + } + + fn from_burns(burns: &[BurnItem]) -> impl Iterator + '_ { + burns.iter().map(Self::from_burn) + } +} + +impl std::ops::AddAssign for AssetStateChange { + fn add_assign(&mut self, rhs: Self) { + self.is_finalized |= rhs.is_finalized; + self.supply_change += rhs.supply_change; + } +} + +/// A map of changes to apply to the issued assets map. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct IssuedAssetsChange(HashMap); + +impl IssuedAssetsChange { + fn new() -> Self { + Self(HashMap::new()) + } + + fn update<'a>(&mut self, changes: impl Iterator + 'a) { + for (asset_base, change) in changes { + *self.0.entry(asset_base).or_default() += change; + } + } + + /// Accepts a reference to an [`Arc`]. + /// + /// Returns a tuple, ([`IssuedAssetsChange`], [`IssuedAssetsChange`]), where + /// the first item is from burns and the second one is for issuance. + pub fn from_block(block: &Arc) -> (Self, Self) { + let mut burn_change = Self::new(); + let mut issuance_change = Self::new(); + + for transaction in &block.transactions { + burn_change.update(AssetStateChange::from_burns(transaction.orchard_burns())); + issuance_change.update(AssetStateChange::from_issue_actions( + transaction.orchard_issue_actions(), + )); + } + + (burn_change, issuance_change) + } +} diff --git a/zebra-chain/src/orchard_zsa/burn.rs b/zebra-chain/src/orchard_zsa/burn.rs index 812728b9380..aea88f619ef 100644 --- a/zebra-chain/src/orchard_zsa/burn.rs +++ b/zebra-chain/src/orchard_zsa/burn.rs @@ -3,7 +3,6 @@ use std::io; use crate::{ - amount::Amount, block::MAX_BLOCK_BYTES, serialization::{SerializationError, TrustedPreallocate, ZcashDeserialize, ZcashSerialize}, }; @@ -19,15 +18,27 @@ const AMOUNT_SIZE: u64 = 8; const BURN_ITEM_SIZE: u64 = ASSET_BASE_SIZE + AMOUNT_SIZE; /// Orchard ZSA burn item. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct BurnItem(AssetBase, Amount); +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct BurnItem(AssetBase, u64); + +impl BurnItem { + /// Returns [`AssetBase`] being burned. + pub fn asset(&self) -> AssetBase { + self.0 + } + + /// Returns [`u64`] representing amount being burned. + pub fn amount(&self) -> u64 { + self.1 + } +} // Convert from burn item type used in `orchard` crate impl TryFrom<(AssetBase, NoteValue)> for BurnItem { type Error = crate::amount::Error; fn try_from(item: (AssetBase, NoteValue)) -> Result { - Ok(Self(item.0, item.1.inner().try_into()?)) + Ok(Self(item.0, item.1.inner())) } } @@ -36,7 +47,7 @@ impl ZcashSerialize for BurnItem { let BurnItem(asset_base, amount) = self; asset_base.zcash_serialize(&mut writer)?; - amount.zcash_serialize(&mut writer)?; + writer.write_all(&amount.to_be_bytes())?; Ok(()) } @@ -44,9 +55,11 @@ impl ZcashSerialize for BurnItem { impl ZcashDeserialize for BurnItem { fn zcash_deserialize(mut reader: R) -> Result { + let mut amount_bytes = [0; 8]; + reader.read_exact(&mut amount_bytes)?; Ok(Self( AssetBase::zcash_deserialize(&mut reader)?, - Amount::zcash_deserialize(&mut reader)?, + u64::from_be_bytes(amount_bytes), )) } } @@ -76,7 +89,7 @@ impl<'de> serde::Deserialize<'de> for BurnItem { D: serde::Deserializer<'de>, { // FIXME: consider another implementation (explicit specifying of [u8; 32] may not look perfect) - let (asset_base_bytes, amount) = <([u8; 32], Amount)>::deserialize(deserializer)?; + let (asset_base_bytes, amount) = <([u8; 32], u64)>::deserialize(deserializer)?; // FIXME: return custom error with a meaningful description? Ok(BurnItem( // FIXME: duplicates the body of AssetBase::zcash_deserialize? @@ -93,6 +106,12 @@ impl<'de> serde::Deserialize<'de> for BurnItem { #[derive(Default, Clone, Debug, PartialEq, Eq, Serialize)] pub struct NoBurn; +impl AsRef<[BurnItem]> for NoBurn { + fn as_ref(&self) -> &[BurnItem] { + &[] + } +} + impl ZcashSerialize for NoBurn { fn zcash_serialize(&self, mut _writer: W) -> Result<(), io::Error> { Ok(()) @@ -115,6 +134,12 @@ impl From> for Burn { } } +impl AsRef<[BurnItem]> for Burn { + fn as_ref(&self) -> &[BurnItem] { + &self.0 + } +} + impl ZcashSerialize for Burn { fn zcash_serialize(&self, writer: W) -> Result<(), io::Error> { self.0.zcash_serialize(writer) diff --git a/zebra-chain/src/orchard_zsa/issuance.rs b/zebra-chain/src/orchard_zsa/issuance.rs index 9f7b4e9faaf..0670419b745 100644 --- a/zebra-chain/src/orchard_zsa/issuance.rs +++ b/zebra-chain/src/orchard_zsa/issuance.rs @@ -57,6 +57,11 @@ impl IssueData { }) }) } + + /// Returns issuance actions + pub fn actions(&self) -> &NonEmpty { + self.0.actions() + } } // Sizes of the serialized values for types in bytes (used for TrustedPreallocate impls) diff --git a/zebra-chain/src/transaction.rs b/zebra-chain/src/transaction.rs index 737253d6eab..c72002c4b76 100644 --- a/zebra-chain/src/transaction.rs +++ b/zebra-chain/src/transaction.rs @@ -78,6 +78,31 @@ macro_rules! orchard_shielded_data_iter { }; } +macro_rules! orchard_shielded_data_map { + ($self:expr, $mapper:expr, $mapper2:expr) => { + match $self { + Transaction::V5 { + orchard_shielded_data: Some(shielded_data), + .. + } => $mapper(shielded_data), + + #[cfg(feature = "tx-v6")] + Transaction::V6 { + orchard_shielded_data: Some(shielded_data), + .. + } => $mapper2(shielded_data), + + // No Orchard shielded data + Transaction::V1 { .. } + | Transaction::V2 { .. } + | Transaction::V3 { .. } + | Transaction::V4 { .. } + | Transaction::V5 { .. } + | Transaction::V6 { .. } => &[], + } + }; +} + // FIXME: doc this // Move down macro_rules! orchard_shielded_data_field { @@ -1071,6 +1096,45 @@ impl Transaction { } } + /// Access the Orchard issue data in this transaction, if any, + /// regardless of version. + #[cfg(feature = "tx-v6")] + fn orchard_issue_data(&self) -> &Option { + match self { + Transaction::V1 { .. } + | Transaction::V2 { .. } + | Transaction::V3 { .. } + | Transaction::V4 { .. } + | Transaction::V5 { .. } => &None, + + Transaction::V6 { + orchard_zsa_issue_data, + .. + } => orchard_zsa_issue_data, + } + } + + /// Access the Orchard issuance actions in this transaction, if there are any, + /// regardless of version. + #[cfg(feature = "tx-v6")] + pub fn orchard_issue_actions(&self) -> impl Iterator { + self.orchard_issue_data() + .iter() + .flat_map(orchard_zsa::IssueData::actions) + } + + /// Access the Orchard asset burns in this transaction, if there are any, + /// regardless of version. + #[cfg(feature = "tx-v6")] + pub fn orchard_burns<'a>(&'a self) -> &[orchard_zsa::BurnItem] { + use crate::orchard::{OrchardVanilla, OrchardZSA}; + orchard_shielded_data_map!( + self, + |data: &'a orchard::ShieldedData| data.burn.as_ref(), + |data: &'a orchard::ShieldedData| data.burn.as_ref() + ) + } + /// Access the [`orchard::Flags`] in this transaction, if there is any, /// regardless of version. pub fn orchard_flags(&self) -> Option { diff --git a/zebra-consensus/src/block.rs b/zebra-consensus/src/block.rs index 611aea2ceba..42a1fbddbd6 100644 --- a/zebra-consensus/src/block.rs +++ b/zebra-consensus/src/block.rs @@ -24,6 +24,7 @@ use tracing::Instrument; use zebra_chain::{ amount::Amount, block, + orchard_zsa::IssuedAssetsChange, parameters::{subsidy::FundingStreamReceiver, Network}, transparent, work::equihash, @@ -314,6 +315,8 @@ where let new_outputs = Arc::into_inner(known_utxos) .expect("all verification tasks using known_utxos are complete"); + let (issued_assets_burns_change, issued_assets_issuance_change) = + IssuedAssetsChange::from_block(&block); let prepared_block = zs::SemanticallyVerifiedBlock { block, hash, @@ -321,6 +324,8 @@ where new_outputs, transaction_hashes, deferred_balance: Some(expected_deferred_amount), + issued_assets_burns_change, + issued_assets_issuance_change, }; // Return early for proposal requests when getblocktemplate-rpcs feature is enabled diff --git a/zebra-state/src/arbitrary.rs b/zebra-state/src/arbitrary.rs index 5c0b837566a..37337029391 100644 --- a/zebra-state/src/arbitrary.rs +++ b/zebra-state/src/arbitrary.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use zebra_chain::{ amount::Amount, block::{self, Block}, + orchard_zsa::IssuedAssetsChange, transaction::Transaction, transparent, value_balance::ValueBalance, @@ -30,6 +31,8 @@ impl Prepare for Arc { let transaction_hashes: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect(); let new_outputs = transparent::new_ordered_outputs_with_height(&block, height, &transaction_hashes); + let (issued_assets_burns_change, issued_assets_issuance_change) = + IssuedAssetsChange::from_block(&block); SemanticallyVerifiedBlock { block, @@ -38,6 +41,8 @@ impl Prepare for Arc { new_outputs, transaction_hashes, deferred_balance: None, + issued_assets_burns_change, + issued_assets_issuance_change, } } } @@ -112,6 +117,8 @@ impl ContextuallyVerifiedBlock { new_outputs, transaction_hashes, deferred_balance: _, + issued_assets_burns_change: _, + issued_assets_issuance_change: _, } = block.into(); Self { diff --git a/zebra-state/src/request.rs b/zebra-state/src/request.rs index 56be011d48e..14c9f73d66c 100644 --- a/zebra-state/src/request.rs +++ b/zebra-state/src/request.rs @@ -11,6 +11,7 @@ use zebra_chain::{ block::{self, Block}, history_tree::HistoryTree, orchard, + orchard_zsa::IssuedAssetsChange, parallel::tree::NoteCommitmentTrees, sapling, serialization::SerializationError, @@ -163,6 +164,12 @@ pub struct SemanticallyVerifiedBlock { pub transaction_hashes: Arc<[transaction::Hash]>, /// This block's contribution to the deferred pool. pub deferred_balance: Option>, + /// A map of burns to be applied to the issued assets map. + // TODO: Reference ZIP. + pub issued_assets_burns_change: IssuedAssetsChange, + /// A map of issuance to be applied to the issued assets map. + // TODO: Reference ZIP. + pub issued_assets_issuance_change: IssuedAssetsChange, } /// A block ready to be committed directly to the finalized state with @@ -392,6 +399,8 @@ impl ContextuallyVerifiedBlock { new_outputs, transaction_hashes, deferred_balance, + issued_assets_burns_change: _, + issued_assets_issuance_change: _, } = semantically_verified; // This is redundant for the non-finalized state, @@ -445,6 +454,8 @@ impl SemanticallyVerifiedBlock { .expect("semantically verified block should have a coinbase height"); let transaction_hashes: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect(); let new_outputs = transparent::new_ordered_outputs(&block, &transaction_hashes); + let (issued_assets_burns_change, issued_assets_issuance_change) = + IssuedAssetsChange::from_block(&block); Self { block, @@ -453,6 +464,8 @@ impl SemanticallyVerifiedBlock { new_outputs, transaction_hashes, deferred_balance: None, + issued_assets_burns_change, + issued_assets_issuance_change, } } @@ -477,6 +490,8 @@ impl From> for SemanticallyVerifiedBlock { .expect("semantically verified block should have a coinbase height"); let transaction_hashes: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect(); let new_outputs = transparent::new_ordered_outputs(&block, &transaction_hashes); + let (issued_assets_burns_change, issued_assets_issuance_change) = + IssuedAssetsChange::from_block(&block); Self { block, @@ -485,12 +500,17 @@ impl From> for SemanticallyVerifiedBlock { new_outputs, transaction_hashes, deferred_balance: None, + issued_assets_burns_change, + issued_assets_issuance_change, } } } impl From for SemanticallyVerifiedBlock { fn from(valid: ContextuallyVerifiedBlock) -> Self { + let (issued_assets_burns_change, issued_assets_issuance_change) = + IssuedAssetsChange::from_block(&valid.block); + Self { block: valid.block, hash: valid.hash, @@ -504,12 +524,17 @@ impl From for SemanticallyVerifiedBlock { .constrain::() .expect("deferred balance in a block must me non-negative"), ), + issued_assets_burns_change, + issued_assets_issuance_change, } } } impl From for SemanticallyVerifiedBlock { fn from(finalized: FinalizedBlock) -> Self { + let (issued_assets_burns_change, issued_assets_issuance_change) = + IssuedAssetsChange::from_block(&finalized.block); + Self { block: finalized.block, hash: finalized.hash, @@ -517,6 +542,8 @@ impl From for SemanticallyVerifiedBlock { new_outputs: finalized.new_outputs, transaction_hashes: finalized.transaction_hashes, deferred_balance: finalized.deferred_balance, + issued_assets_burns_change, + issued_assets_issuance_change, } } } diff --git a/zebra-state/src/service/chain_tip.rs b/zebra-state/src/service/chain_tip.rs index 04ea61d6982..af1ec34dc56 100644 --- a/zebra-state/src/service/chain_tip.rs +++ b/zebra-state/src/service/chain_tip.rs @@ -116,6 +116,8 @@ impl From for ChainTipBlock { new_outputs: _, transaction_hashes, deferred_balance: _, + issued_assets_burns_change: _, + issued_assets_issuance_change: _, } = prepared; Self { diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs b/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs index 194f2202a87..323e41fd7ed 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs @@ -20,6 +20,7 @@ use zebra_chain::{ }, Block, Height, }, + orchard_zsa::IssuedAssetsChange, parameters::Network::{self, *}, serialization::{ZcashDeserializeInto, ZcashSerialize}, transparent::new_ordered_outputs_with_height, @@ -129,6 +130,8 @@ fn test_block_db_round_trip_with( .collect(); let new_outputs = new_ordered_outputs_with_height(&original_block, Height(0), &transaction_hashes); + let (issued_assets_burns_change, issued_assets_issuance_change) = + IssuedAssetsChange::from_block(&original_block); CheckpointVerifiedBlock(SemanticallyVerifiedBlock { block: original_block.clone(), @@ -137,6 +140,8 @@ fn test_block_db_round_trip_with( new_outputs, transaction_hashes, deferred_balance: None, + issued_assets_burns_change, + issued_assets_issuance_change, }) }; From cc8bc0da97d12cd34bc6987311a05c9a26e23c4c Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 12 Nov 2024 01:07:20 -0500 Subject: [PATCH 02/35] Adds issued assets to the finalized state --- zebra-chain/src/orchard_zsa.rs | 2 +- zebra-chain/src/orchard_zsa/asset_state.rs | 71 ++++++++++++++- zebra-consensus/src/block.rs | 11 +-- zebra-state/src/arbitrary.rs | 15 ++-- zebra-state/src/lib.rs | 3 +- zebra-state/src/request.rs | 90 ++++++++++++++----- zebra-state/src/service/chain_tip.rs | 3 +- .../finalized_state/disk_format/shielded.rs | 47 +++++++++- .../service/finalized_state/zebra_db/block.rs | 2 +- .../zebra_db/block/tests/vectors.rs | 11 +-- .../finalized_state/zebra_db/shielded.rs | 63 ++++++++++++- 11 files changed, 267 insertions(+), 51 deletions(-) diff --git a/zebra-chain/src/orchard_zsa.rs b/zebra-chain/src/orchard_zsa.rs index cbe93771e67..d012e8de4ca 100644 --- a/zebra-chain/src/orchard_zsa.rs +++ b/zebra-chain/src/orchard_zsa.rs @@ -13,4 +13,4 @@ mod issuance; pub(crate) use burn::{Burn, BurnItem, NoBurn}; pub(crate) use issuance::IssueData; -pub use asset_state::{AssetBase, AssetState, AssetStateChange, IssuedAssetsChange}; +pub use asset_state::{AssetBase, AssetState, AssetStateChange, IssuedAssets, IssuedAssetsChange}; diff --git a/zebra-chain/src/orchard_zsa/asset_state.rs b/zebra-chain/src/orchard_zsa/asset_state.rs index bdcba2528fe..5327f531107 100644 --- a/zebra-chain/src/orchard_zsa/asset_state.rs +++ b/zebra-chain/src/orchard_zsa/asset_state.rs @@ -10,7 +10,7 @@ use crate::block::Block; use super::BurnItem; /// The circulating supply and whether that supply has been finalized. -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] pub struct AssetState { /// Indicates whether the asset is finalized such that no more of it can be issued. pub is_finalized: bool, @@ -29,6 +29,17 @@ pub struct AssetStateChange { pub supply_change: i128, } +impl AssetState { + fn with_change(mut self, change: AssetStateChange) -> Self { + self.is_finalized |= change.is_finalized; + self.total_supply = self + .total_supply + .checked_add_signed(change.supply_change) + .expect("burn amounts must not be greater than initial supply"); + self + } +} + impl AssetStateChange { fn from_note(is_finalized: bool, note: orchard::Note) -> (AssetBase, Self) { ( @@ -77,6 +88,33 @@ impl std::ops::AddAssign for AssetStateChange { } } +/// An `issued_asset` map +// TODO: Reference ZIP +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct IssuedAssets(HashMap); + +impl IssuedAssets { + fn new() -> Self { + Self(HashMap::new()) + } + + fn update<'a>(&mut self, issued_assets: impl Iterator + 'a) { + for (asset_base, asset_state) in issued_assets { + self.0.insert(asset_base, asset_state); + } + } +} + +impl IntoIterator for IssuedAssets { + type Item = (AssetBase, AssetState); + + type IntoIter = std::collections::hash_map::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + /// A map of changes to apply to the issued assets map. #[derive(Clone, Debug, PartialEq, Eq)] pub struct IssuedAssetsChange(HashMap); @@ -109,4 +147,35 @@ impl IssuedAssetsChange { (burn_change, issuance_change) } + + /// Consumes self and accepts a closure for looking up previous asset states. + /// + /// Applies changes in self to the previous asset state. + /// + /// Returns an [`IssuedAssets`] with the updated asset states. + pub fn apply_with(self, f: impl Fn(AssetBase) -> AssetState) -> IssuedAssets { + let mut issued_assets = IssuedAssets::new(); + + issued_assets.update( + self.0 + .into_iter() + .map(|(asset_base, change)| (asset_base, f(asset_base).with_change(change))), + ); + + issued_assets + } +} + +impl std::ops::Add for IssuedAssetsChange { + type Output = Self; + + fn add(mut self, mut rhs: Self) -> Self { + if self.0.len() > rhs.0.len() { + self.update(rhs.0.into_iter()); + self + } else { + rhs.update(self.0.into_iter()); + rhs + } + } } diff --git a/zebra-consensus/src/block.rs b/zebra-consensus/src/block.rs index 42a1fbddbd6..46ca02e0a08 100644 --- a/zebra-consensus/src/block.rs +++ b/zebra-consensus/src/block.rs @@ -29,7 +29,7 @@ use zebra_chain::{ transparent, work::equihash, }; -use zebra_state as zs; +use zebra_state::{self as zs, IssuedAssetsOrChanges}; use crate::{error::*, transaction as tx, BoxError}; @@ -315,8 +315,7 @@ where let new_outputs = Arc::into_inner(known_utxos) .expect("all verification tasks using known_utxos are complete"); - let (issued_assets_burns_change, issued_assets_issuance_change) = - IssuedAssetsChange::from_block(&block); + let (burns, issuance) = IssuedAssetsChange::from_block(&block); let prepared_block = zs::SemanticallyVerifiedBlock { block, hash, @@ -324,8 +323,10 @@ where new_outputs, transaction_hashes, deferred_balance: Some(expected_deferred_amount), - issued_assets_burns_change, - issued_assets_issuance_change, + issued_assets_changes: IssuedAssetsOrChanges::BurnAndIssuanceChanges { + burns, + issuance, + }, }; // Return early for proposal requests when getblocktemplate-rpcs feature is enabled diff --git a/zebra-state/src/arbitrary.rs b/zebra-state/src/arbitrary.rs index 37337029391..2c7ff4dd166 100644 --- a/zebra-state/src/arbitrary.rs +++ b/zebra-state/src/arbitrary.rs @@ -12,7 +12,8 @@ use zebra_chain::{ }; use crate::{ - request::ContextuallyVerifiedBlock, service::chain_tip::ChainTipBlock, + request::{ContextuallyVerifiedBlock, IssuedAssetsOrChanges}, + service::chain_tip::ChainTipBlock, SemanticallyVerifiedBlock, }; @@ -31,8 +32,7 @@ impl Prepare for Arc { let transaction_hashes: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect(); let new_outputs = transparent::new_ordered_outputs_with_height(&block, height, &transaction_hashes); - let (issued_assets_burns_change, issued_assets_issuance_change) = - IssuedAssetsChange::from_block(&block); + let (burns, issuance) = IssuedAssetsChange::from_block(&block); SemanticallyVerifiedBlock { block, @@ -41,8 +41,10 @@ impl Prepare for Arc { new_outputs, transaction_hashes, deferred_balance: None, - issued_assets_burns_change, - issued_assets_issuance_change, + issued_assets_changes: IssuedAssetsOrChanges::BurnAndIssuanceChanges { + burns, + issuance, + }, } } } @@ -117,8 +119,7 @@ impl ContextuallyVerifiedBlock { new_outputs, transaction_hashes, deferred_balance: _, - issued_assets_burns_change: _, - issued_assets_issuance_change: _, + issued_assets_changes: _, } = block.into(); Self { diff --git a/zebra-state/src/lib.rs b/zebra-state/src/lib.rs index e93a3b8f905..58010fb648e 100644 --- a/zebra-state/src/lib.rs +++ b/zebra-state/src/lib.rs @@ -42,7 +42,8 @@ pub use error::{ ValidateContextError, }; pub use request::{ - CheckpointVerifiedBlock, HashOrHeight, ReadRequest, Request, SemanticallyVerifiedBlock, + CheckpointVerifiedBlock, HashOrHeight, IssuedAssetsOrChanges, ReadRequest, Request, + SemanticallyVerifiedBlock, }; pub use response::{KnownBlock, MinedTx, ReadResponse, Response}; pub use service::{ diff --git a/zebra-state/src/request.rs b/zebra-state/src/request.rs index 14c9f73d66c..7d852dba638 100644 --- a/zebra-state/src/request.rs +++ b/zebra-state/src/request.rs @@ -11,7 +11,7 @@ use zebra_chain::{ block::{self, Block}, history_tree::HistoryTree, orchard, - orchard_zsa::IssuedAssetsChange, + orchard_zsa::{IssuedAssets, IssuedAssetsChange}, parallel::tree::NoteCommitmentTrees, sapling, serialization::SerializationError, @@ -166,10 +166,7 @@ pub struct SemanticallyVerifiedBlock { pub deferred_balance: Option>, /// A map of burns to be applied to the issued assets map. // TODO: Reference ZIP. - pub issued_assets_burns_change: IssuedAssetsChange, - /// A map of issuance to be applied to the issued assets map. - // TODO: Reference ZIP. - pub issued_assets_issuance_change: IssuedAssetsChange, + pub issued_assets_changes: IssuedAssetsOrChanges, } /// A block ready to be committed directly to the finalized state with @@ -300,6 +297,48 @@ pub struct FinalizedBlock { pub(super) treestate: Treestate, /// This block's contribution to the deferred pool. pub(super) deferred_balance: Option>, + /// Either changes to be applied to the previous `issued_assets` map for the finalized tip, or + /// updates asset states to be inserted into the finalized state, replacing the previous + /// asset states for those asset bases. + pub issued_assets: IssuedAssetsOrChanges, +} + +/// Either changes to be applied to the previous `issued_assets` map for the finalized tip, or +/// updates asset states to be inserted into the finalized state, replacing the previous +/// asset states for those asset bases. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum IssuedAssetsOrChanges { + /// A map of updated issued assets. + State(IssuedAssets), + + /// A map of changes to apply to the issued assets map. + Change(IssuedAssetsChange), + + /// A map of changes from burns and issuance to apply to the issued assets map. + BurnAndIssuanceChanges { + /// A map of changes from burns to apply to the issued assets map. + burns: IssuedAssetsChange, + /// A map of changes from issuance to apply to the issued assets map. + issuance: IssuedAssetsChange, + }, +} + +impl IssuedAssetsOrChanges { + /// Combines fields in the `BurnAndIssuanceChanges` variant then returns a `Change` variant, or + /// returns self unmodified. + pub fn combine(self) -> Self { + let Self::BurnAndIssuanceChanges { burns, issuance } = self else { + return self; + }; + + Self::Change(burns + issuance) + } +} + +impl From for IssuedAssetsOrChanges { + fn from(change: IssuedAssetsChange) -> Self { + Self::Change(change) + } } impl FinalizedBlock { @@ -326,6 +365,7 @@ impl FinalizedBlock { transaction_hashes: block.transaction_hashes, treestate, deferred_balance: block.deferred_balance, + issued_assets: block.issued_assets_changes.combine(), } } } @@ -399,8 +439,7 @@ impl ContextuallyVerifiedBlock { new_outputs, transaction_hashes, deferred_balance, - issued_assets_burns_change: _, - issued_assets_issuance_change: _, + issued_assets_changes: _, } = semantically_verified; // This is redundant for the non-finalized state, @@ -454,8 +493,7 @@ impl SemanticallyVerifiedBlock { .expect("semantically verified block should have a coinbase height"); let transaction_hashes: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect(); let new_outputs = transparent::new_ordered_outputs(&block, &transaction_hashes); - let (issued_assets_burns_change, issued_assets_issuance_change) = - IssuedAssetsChange::from_block(&block); + let (burns, issuance) = IssuedAssetsChange::from_block(&block); Self { block, @@ -464,8 +502,11 @@ impl SemanticallyVerifiedBlock { new_outputs, transaction_hashes, deferred_balance: None, - issued_assets_burns_change, - issued_assets_issuance_change, + issued_assets_changes: IssuedAssetsOrChanges::BurnAndIssuanceChanges { + burns, + issuance, + } + .combine(), } } @@ -490,8 +531,7 @@ impl From> for SemanticallyVerifiedBlock { .expect("semantically verified block should have a coinbase height"); let transaction_hashes: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect(); let new_outputs = transparent::new_ordered_outputs(&block, &transaction_hashes); - let (issued_assets_burns_change, issued_assets_issuance_change) = - IssuedAssetsChange::from_block(&block); + let (burns, issuance) = IssuedAssetsChange::from_block(&block); Self { block, @@ -500,16 +540,17 @@ impl From> for SemanticallyVerifiedBlock { new_outputs, transaction_hashes, deferred_balance: None, - issued_assets_burns_change, - issued_assets_issuance_change, + issued_assets_changes: IssuedAssetsOrChanges::BurnAndIssuanceChanges { + burns, + issuance, + }, } } } impl From for SemanticallyVerifiedBlock { fn from(valid: ContextuallyVerifiedBlock) -> Self { - let (issued_assets_burns_change, issued_assets_issuance_change) = - IssuedAssetsChange::from_block(&valid.block); + let (burns, issuance) = IssuedAssetsChange::from_block(&valid.block); Self { block: valid.block, @@ -524,16 +565,17 @@ impl From for SemanticallyVerifiedBlock { .constrain::() .expect("deferred balance in a block must me non-negative"), ), - issued_assets_burns_change, - issued_assets_issuance_change, + issued_assets_changes: IssuedAssetsOrChanges::BurnAndIssuanceChanges { + burns, + issuance, + }, } } } impl From for SemanticallyVerifiedBlock { fn from(finalized: FinalizedBlock) -> Self { - let (issued_assets_burns_change, issued_assets_issuance_change) = - IssuedAssetsChange::from_block(&finalized.block); + let (burns, issuance) = IssuedAssetsChange::from_block(&finalized.block); Self { block: finalized.block, @@ -542,8 +584,10 @@ impl From for SemanticallyVerifiedBlock { new_outputs: finalized.new_outputs, transaction_hashes: finalized.transaction_hashes, deferred_balance: finalized.deferred_balance, - issued_assets_burns_change, - issued_assets_issuance_change, + issued_assets_changes: IssuedAssetsOrChanges::BurnAndIssuanceChanges { + burns, + issuance, + }, } } } diff --git a/zebra-state/src/service/chain_tip.rs b/zebra-state/src/service/chain_tip.rs index af1ec34dc56..8a0ed517766 100644 --- a/zebra-state/src/service/chain_tip.rs +++ b/zebra-state/src/service/chain_tip.rs @@ -116,8 +116,7 @@ impl From for ChainTipBlock { new_outputs: _, transaction_hashes, deferred_balance: _, - issued_assets_burns_change: _, - issued_assets_issuance_change: _, + issued_assets_changes: _, } = prepared; Self { 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 bcd24d5c604..953815cae4c 100644 --- a/zebra-state/src/service/finalized_state/disk_format/shielded.rs +++ b/zebra-state/src/service/finalized_state/disk_format/shielded.rs @@ -9,7 +9,9 @@ use bincode::Options; use zebra_chain::{ block::Height, - orchard, sapling, sprout, + orchard, + orchard_zsa::{AssetBase, AssetState}, + sapling, sprout, subtree::{NoteCommitmentSubtreeData, NoteCommitmentSubtreeIndex}, }; @@ -207,3 +209,46 @@ impl FromDisk for NoteCommitmentSubtreeData { ) } } + +// TODO: Replace `.unwrap()`s with `.expect()`s + +impl IntoDisk for AssetState { + type Bytes = [u8; 9]; + + fn as_bytes(&self) -> Self::Bytes { + [ + vec![self.is_finalized as u8], + self.total_supply.to_be_bytes().to_vec(), + ] + .concat() + .try_into() + .unwrap() + } +} + +impl FromDisk for AssetState { + fn from_bytes(bytes: impl AsRef<[u8]>) -> Self { + let (&is_finalized_byte, bytes) = bytes.as_ref().split_first().unwrap(); + let (&total_supply_bytes, _bytes) = bytes.split_first_chunk().unwrap(); + + Self { + is_finalized: is_finalized_byte != 0, + total_supply: u64::from_be_bytes(total_supply_bytes).into(), + } + } +} + +impl IntoDisk for AssetBase { + type Bytes = [u8; 32]; + + fn as_bytes(&self) -> Self::Bytes { + self.to_bytes() + } +} + +impl FromDisk for AssetBase { + fn from_bytes(bytes: impl AsRef<[u8]>) -> Self { + let (asset_base_bytes, _) = bytes.as_ref().split_first_chunk().unwrap(); + Self::from_bytes(asset_base_bytes).unwrap() + } +} 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 4dc3a801ef3..6f0d2340b91 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/block.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/block.rs @@ -463,7 +463,7 @@ impl DiskWriteBatch { // which is already present from height 1 to the first shielded transaction. // // In Zebra we include the nullifiers and note commitments in the genesis block because it simplifies our code. - self.prepare_shielded_transaction_batch(db, finalized)?; + self.prepare_shielded_transaction_batch(zebra_db, finalized)?; self.prepare_trees_batch(zebra_db, finalized, prev_note_commitment_trees)?; // # Consensus diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs b/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs index 323e41fd7ed..45555b969bf 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs @@ -29,7 +29,7 @@ use zebra_test::vectors::{MAINNET_BLOCKS, TESTNET_BLOCKS}; use crate::{ constants::{state_database_format_version_in_code, STATE_DATABASE_KIND}, - request::{FinalizedBlock, Treestate}, + request::{FinalizedBlock, IssuedAssetsOrChanges, Treestate}, service::finalized_state::{disk_db::DiskWriteBatch, ZebraDb, STATE_COLUMN_FAMILIES_IN_CODE}, CheckpointVerifiedBlock, Config, SemanticallyVerifiedBlock, }; @@ -130,8 +130,7 @@ fn test_block_db_round_trip_with( .collect(); let new_outputs = new_ordered_outputs_with_height(&original_block, Height(0), &transaction_hashes); - let (issued_assets_burns_change, issued_assets_issuance_change) = - IssuedAssetsChange::from_block(&original_block); + let (burns, issuance) = IssuedAssetsChange::from_block(&original_block); CheckpointVerifiedBlock(SemanticallyVerifiedBlock { block: original_block.clone(), @@ -140,8 +139,10 @@ fn test_block_db_round_trip_with( new_outputs, transaction_hashes, deferred_balance: None, - issued_assets_burns_change, - issued_assets_issuance_change, + issued_assets_changes: IssuedAssetsOrChanges::BurnAndIssuanceChanges { + burns, + issuance, + }, }) }; 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 4bba75b1891..02756265999 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/shielded.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/shielded.rs @@ -19,7 +19,8 @@ use std::{ use zebra_chain::{ block::Height, - orchard, + orchard::{self}, + orchard_zsa::{AssetBase, AssetState}, parallel::tree::NoteCommitmentTrees, sapling, sprout, subtree::{NoteCommitmentSubtreeData, NoteCommitmentSubtreeIndex}, @@ -33,14 +34,31 @@ use crate::{ disk_format::RawBytes, zebra_db::ZebraDb, }, - BoxError, + BoxError, IssuedAssetsOrChanges, TypedColumnFamily, }; // Doc-only items #[allow(unused_imports)] use zebra_chain::subtree::NoteCommitmentSubtree; +/// The name of the chain value pools column family. +/// +/// This constant should be used so the compiler can detect typos. +pub const ISSUED_ASSETS: &str = "orchard_issued_assets"; + +/// 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 IssuedAssetsCf<'cf> = TypedColumnFamily<'cf, AssetBase, AssetState>; + impl ZebraDb { + /// Returns a typed handle to the `history_tree` column family. + pub(crate) fn issued_assets_cf(&self) -> IssuedAssetsCf { + IssuedAssetsCf::new(&self.db, ISSUED_ASSETS) + .expect("column family was created when database was created") + } + // Read shielded methods /// Returns `true` if the finalized state contains `sprout_nullifier`. @@ -410,6 +428,11 @@ impl ZebraDb { Some(subtree_data.with_index(index)) } + /// Get the orchard issued asset state for the finalized tip. + pub fn issued_asset(&self, asset_base: &AssetBase) -> Option { + self.issued_assets_cf().zs_get(asset_base) + } + /// Returns the shielded note commitment trees of the finalized tip /// or the empty trees if the state is empty. /// Additionally, returns the sapling and orchard subtrees for the finalized tip if @@ -437,16 +460,18 @@ impl DiskWriteBatch { /// - Propagates any errors from updating note commitment trees pub fn prepare_shielded_transaction_batch( &mut self, - db: &DiskDb, + zebra_db: &ZebraDb, finalized: &FinalizedBlock, ) -> Result<(), BoxError> { let FinalizedBlock { block, .. } = finalized; // Index each transaction's shielded data for transaction in &block.transactions { - self.prepare_nullifier_batch(db, transaction)?; + self.prepare_nullifier_batch(&zebra_db.db, transaction)?; } + self.prepare_issued_assets_batch(zebra_db, &finalized.issued_assets)?; + Ok(()) } @@ -480,6 +505,36 @@ impl DiskWriteBatch { Ok(()) } + /// Prepare a database batch containing `finalized.block`'s asset issuance + /// and return it (without actually writing anything). + /// + /// # Errors + /// + /// - This method doesn't currently return any errors, but it might in future + #[allow(clippy::unwrap_in_result)] + pub fn prepare_issued_assets_batch( + &mut self, + zebra_db: &ZebraDb, + issued_assets_or_changes: &IssuedAssetsOrChanges, + ) -> Result<(), BoxError> { + let mut batch = zebra_db.issued_assets_cf().with_batch_for_writing(self); + + let updated_issued_assets = match issued_assets_or_changes.clone().combine() { + IssuedAssetsOrChanges::State(issued_assets) => issued_assets, + IssuedAssetsOrChanges::Change(issued_assets_change) => issued_assets_change + .apply_with(|asset_base| zebra_db.issued_asset(&asset_base).unwrap_or_default()), + IssuedAssetsOrChanges::BurnAndIssuanceChanges { .. } => { + panic!("unexpected variant returned from `combine()`") + } + }; + + for (asset_base, updated_issued_asset_state) in updated_issued_assets { + batch = batch.zs_insert(&asset_base, &updated_issued_asset_state); + } + + Ok(()) + } + /// Prepare a database batch containing the note commitment and history tree updates /// from `finalized.block`, and return it (without actually writing anything). /// From c7116f33b13db13dd9092be75d334e70f48faca2 Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 12 Nov 2024 01:56:15 -0500 Subject: [PATCH 03/35] Validates issuance actions and burns before committing blocks to a non-finalized chain. --- zebra-chain/src/orchard_zsa/asset_state.rs | 47 +++++++++++---- zebra-state/src/error.rs | 6 ++ zebra-state/src/request.rs | 2 +- zebra-state/src/service/check.rs | 1 + zebra-state/src/service/check/issuance.rs | 58 +++++++++++++++++++ .../src/service/non_finalized_state.rs | 3 + .../src/service/non_finalized_state/chain.rs | 14 ++++- 7 files changed, 116 insertions(+), 15 deletions(-) create mode 100644 zebra-state/src/service/check/issuance.rs diff --git a/zebra-chain/src/orchard_zsa/asset_state.rs b/zebra-chain/src/orchard_zsa/asset_state.rs index 5327f531107..8255da9cce7 100644 --- a/zebra-chain/src/orchard_zsa/asset_state.rs +++ b/zebra-chain/src/orchard_zsa/asset_state.rs @@ -30,13 +30,22 @@ pub struct AssetStateChange { } impl AssetState { - fn with_change(mut self, change: AssetStateChange) -> Self { + /// Updates and returns self with the provided [`AssetStateChange`] if the change is valid, or + /// returns None otherwise. + pub fn with_change(mut self, change: AssetStateChange) -> Option { + if self.is_finalized { + return None; + } + self.is_finalized |= change.is_finalized; - self.total_supply = self - .total_supply - .checked_add_signed(change.supply_change) - .expect("burn amounts must not be greater than initial supply"); - self + self.total_supply = self.total_supply.checked_add_signed(change.supply_change)?; + Some(self) + } +} + +impl From> for IssuedAssets { + fn from(issued_assets: HashMap) -> Self { + Self(issued_assets) } } @@ -94,7 +103,8 @@ impl std::ops::AddAssign for AssetStateChange { pub struct IssuedAssets(HashMap); impl IssuedAssets { - fn new() -> Self { + /// Creates a new [`IssuedAssets`]. + pub fn new() -> Self { Self(HashMap::new()) } @@ -156,11 +166,14 @@ impl IssuedAssetsChange { pub fn apply_with(self, f: impl Fn(AssetBase) -> AssetState) -> IssuedAssets { let mut issued_assets = IssuedAssets::new(); - issued_assets.update( - self.0 - .into_iter() - .map(|(asset_base, change)| (asset_base, f(asset_base).with_change(change))), - ); + issued_assets.update(self.0.into_iter().map(|(asset_base, change)| { + ( + asset_base, + f(asset_base) + .with_change(change) + .expect("must be valid change"), + ) + })); issued_assets } @@ -179,3 +192,13 @@ impl std::ops::Add for IssuedAssetsChange { } } } + +impl IntoIterator for IssuedAssetsChange { + type Item = (AssetBase, AssetStateChange); + + type IntoIter = std::collections::hash_map::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} diff --git a/zebra-state/src/error.rs b/zebra-state/src/error.rs index cf495311efb..4a20f5c29d1 100644 --- a/zebra-state/src/error.rs +++ b/zebra-state/src/error.rs @@ -264,6 +264,12 @@ pub enum ValidateContextError { tx_index_in_block: Option, transaction_hash: transaction::Hash, }, + + #[error("burn amounts must be less than issued asset supply")] + InvalidBurn, + + #[error("must not issue finalized assets")] + InvalidIssuance, } /// Trait for creating the corresponding duplicate nullifier error from a nullifier. diff --git a/zebra-state/src/request.rs b/zebra-state/src/request.rs index 7d852dba638..51fa95917c8 100644 --- a/zebra-state/src/request.rs +++ b/zebra-state/src/request.rs @@ -365,7 +365,7 @@ impl FinalizedBlock { transaction_hashes: block.transaction_hashes, treestate, deferred_balance: block.deferred_balance, - issued_assets: block.issued_assets_changes.combine(), + issued_assets: block.issued_assets_changes, } } } 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..610825dd2f2 --- /dev/null +++ b/zebra-state/src/service/check/issuance.rs @@ -0,0 +1,58 @@ +//! Checks for issuance and burn validity. + +use std::{collections::HashMap, sync::Arc}; + +use zebra_chain::orchard_zsa::IssuedAssets; + +use crate::{IssuedAssetsOrChanges, SemanticallyVerifiedBlock, ValidateContextError, ZebraDb}; + +use super::Chain; + +pub fn valid_burns_and_issuance( + finalized_state: &ZebraDb, + parent_chain: &Arc, + semantically_verified: &SemanticallyVerifiedBlock, +) -> Result { + let IssuedAssetsOrChanges::BurnAndIssuanceChanges { burns, issuance } = + semantically_verified.issued_assets_changes.clone() + else { + panic!("unexpected variant in semantically verified block") + }; + + let mut issued_assets = HashMap::new(); + + for (asset_base, burn_change) in burns.clone() { + // TODO: Move this to a read fn. + let updated_asset_state = parent_chain + .issued_asset(&asset_base) + .or_else(|| finalized_state.issued_asset(&asset_base)) + .ok_or(ValidateContextError::InvalidBurn)? + .with_change(burn_change) + .ok_or(ValidateContextError::InvalidBurn)?; + + issued_assets + .insert(asset_base, updated_asset_state) + .expect("transactions must have only one burn item per asset base"); + } + + for (asset_base, issuance_change) in issuance.clone() { + // TODO: Move this to a read fn. + let Some(asset_state) = issued_assets + .get(&asset_base) + .copied() + .or_else(|| parent_chain.issued_asset(&asset_base)) + .or_else(|| finalized_state.issued_asset(&asset_base)) + else { + continue; + }; + + let _ = issued_assets.insert( + asset_base, + asset_state + .with_change(issuance_change) + .ok_or(ValidateContextError::InvalidIssuance)?, + ); + } + + Ok(issued_assets.into()) +} diff --git a/zebra-state/src/service/non_finalized_state.rs b/zebra-state/src/service/non_finalized_state.rs index 08d64455024..11ec27be68c 100644 --- a/zebra-state/src/service/non_finalized_state.rs +++ b/zebra-state/src/service/non_finalized_state.rs @@ -325,6 +325,9 @@ impl NonFinalizedState { finalized_state, )?; + let _issued_assets = + check::issuance::valid_burns_and_issuance(finalized_state, &new_chain, &prepared)?; + // Reads from disk check::anchors::block_sapling_orchard_anchors_refer_to_final_treestates( finalized_state, diff --git a/zebra-state/src/service/non_finalized_state/chain.rs b/zebra-state/src/service/non_finalized_state/chain.rs index d0ce3eee904..2af1df7daeb 100644 --- a/zebra-state/src/service/non_finalized_state/chain.rs +++ b/zebra-state/src/service/non_finalized_state/chain.rs @@ -16,13 +16,16 @@ use zebra_chain::{ block::{self, Height}, history_tree::HistoryTree, orchard, + orchard_zsa::{AssetBase, AssetState}, 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, @@ -937,6 +940,13 @@ impl Chain { } } + /// Returns the Orchard issued asset state if one is present in + /// the chain for the provided asset base. + pub fn issued_asset(&self, _asset_base: &AssetBase) -> Option { + // self.orchard_issued_assets.get(asset_base).cloned() + None + } + /// Adds the Orchard `tree` to the tree and anchor indexes at `height`. /// /// `height` can be either: From bb62c67ba0d69cd9b8be455cb7c4460a19e69d39 Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 12 Nov 2024 02:07:43 -0500 Subject: [PATCH 04/35] Adds `issued_assets` fields on `ChainInner` and `ContextuallyValidatedBlock` --- zebra-state/src/arbitrary.rs | 9 +++++++-- zebra-state/src/request.rs | 6 ++++++ zebra-state/src/service/non_finalized_state.rs | 4 +++- zebra-state/src/service/non_finalized_state/chain.rs | 11 ++++++++--- .../src/service/non_finalized_state/tests/prop.rs | 6 ++++-- 5 files changed, 28 insertions(+), 8 deletions(-) diff --git a/zebra-state/src/arbitrary.rs b/zebra-state/src/arbitrary.rs index 2c7ff4dd166..348e8fa6026 100644 --- a/zebra-state/src/arbitrary.rs +++ b/zebra-state/src/arbitrary.rs @@ -103,8 +103,12 @@ impl ContextuallyVerifiedBlock { .map(|outpoint| (outpoint, zero_utxo.clone())) .collect(); - ContextuallyVerifiedBlock::with_block_and_spent_utxos(block, zero_spent_utxos) - .expect("all UTXOs are provided with zero values") + ContextuallyVerifiedBlock::with_block_and_spent_utxos( + block, + zero_spent_utxos, + Default::default(), + ) + .expect("all UTXOs are provided with zero values") } /// Create a [`ContextuallyVerifiedBlock`] from a [`Block`] or [`SemanticallyVerifiedBlock`], @@ -133,6 +137,7 @@ impl ContextuallyVerifiedBlock { spent_outputs: new_outputs, transaction_hashes, chain_value_pool_change: ValueBalance::zero(), + issued_assets: Default::default(), } } } diff --git a/zebra-state/src/request.rs b/zebra-state/src/request.rs index 51fa95917c8..71fc6049c50 100644 --- a/zebra-state/src/request.rs +++ b/zebra-state/src/request.rs @@ -227,6 +227,10 @@ pub struct ContextuallyVerifiedBlock { /// The sum of the chain value pool changes of all transactions in this block. pub(crate) chain_value_pool_change: ValueBalance, + + /// A partial map of `issued_assets` with entries for asset states that were updated in + /// this block. + pub(crate) issued_assets: IssuedAssets, } /// Wraps note commitment trees and the history tree together. @@ -431,6 +435,7 @@ impl ContextuallyVerifiedBlock { pub fn with_block_and_spent_utxos( semantically_verified: SemanticallyVerifiedBlock, mut spent_outputs: HashMap, + issued_assets: IssuedAssets, ) -> Result { let SemanticallyVerifiedBlock { block, @@ -459,6 +464,7 @@ impl ContextuallyVerifiedBlock { &utxos_from_ordered_utxos(spent_outputs), deferred_balance, )?, + issued_assets, }) } } diff --git a/zebra-state/src/service/non_finalized_state.rs b/zebra-state/src/service/non_finalized_state.rs index 11ec27be68c..1ca33cb43f4 100644 --- a/zebra-state/src/service/non_finalized_state.rs +++ b/zebra-state/src/service/non_finalized_state.rs @@ -325,7 +325,7 @@ impl NonFinalizedState { finalized_state, )?; - let _issued_assets = + let issued_assets = check::issuance::valid_burns_and_issuance(finalized_state, &new_chain, &prepared)?; // Reads from disk @@ -346,6 +346,8 @@ impl NonFinalizedState { let contextual = ContextuallyVerifiedBlock::with_block_and_spent_utxos( prepared.clone(), spent_utxos.clone(), + // TODO: Refactor this into repeated `With::with()` calls, see http_request_compatibility module. + issued_assets, ) .map_err(|value_balance_error| { ValidateContextError::CalculateBlockChainValueChange { diff --git a/zebra-state/src/service/non_finalized_state/chain.rs b/zebra-state/src/service/non_finalized_state/chain.rs index 2af1df7daeb..31ee8c027ba 100644 --- a/zebra-state/src/service/non_finalized_state/chain.rs +++ b/zebra-state/src/service/non_finalized_state/chain.rs @@ -177,6 +177,11 @@ pub struct ChainInner { pub(crate) orchard_subtrees: BTreeMap>, + /// A partial map of `issued_assets` with entries for asset states that were updated in + /// this chain. + // TODO: Add reference to ZIP + pub(crate) issued_assets: HashMap, + // Nullifiers // /// The Sprout nullifiers revealed by `blocks`. @@ -240,6 +245,7 @@ impl Chain { orchard_anchors_by_height: Default::default(), orchard_trees_by_height: Default::default(), orchard_subtrees: Default::default(), + issued_assets: Default::default(), sprout_nullifiers: Default::default(), sapling_nullifiers: Default::default(), orchard_nullifiers: Default::default(), @@ -942,9 +948,8 @@ impl Chain { /// Returns the Orchard issued asset state if one is present in /// the chain for the provided asset base. - pub fn issued_asset(&self, _asset_base: &AssetBase) -> Option { - // self.orchard_issued_assets.get(asset_base).cloned() - None + pub fn issued_asset(&self, asset_base: &AssetBase) -> Option { + self.issued_assets.get(asset_base).cloned() } /// Adds the Orchard `tree` to the tree and anchor indexes at `height`. diff --git a/zebra-state/src/service/non_finalized_state/tests/prop.rs b/zebra-state/src/service/non_finalized_state/tests/prop.rs index 2a1adf65c20..16f3ee84f70 100644 --- a/zebra-state/src/service/non_finalized_state/tests/prop.rs +++ b/zebra-state/src/service/non_finalized_state/tests/prop.rs @@ -52,6 +52,7 @@ fn push_genesis_chain() -> Result<()> { ContextuallyVerifiedBlock::with_block_and_spent_utxos( block, only_chain.unspent_utxos(), + Default::default(), ) .map_err(|e| (e, chain_values.clone())) .expect("invalid block value pool change"); @@ -148,6 +149,7 @@ fn forked_equals_pushed_genesis() -> Result<()> { let block = ContextuallyVerifiedBlock::with_block_and_spent_utxos( block, partial_chain.unspent_utxos(), + Default::default() )?; partial_chain = partial_chain .push(block) @@ -167,7 +169,7 @@ fn forked_equals_pushed_genesis() -> Result<()> { for block in chain.iter().cloned() { let block = - ContextuallyVerifiedBlock::with_block_and_spent_utxos(block, full_chain.unspent_utxos())?; + ContextuallyVerifiedBlock::with_block_and_spent_utxos(block, full_chain.unspent_utxos(), Default::default())?; // Check some properties of the genesis block and don't push it to the chain. if block.height == block::Height(0) { @@ -210,7 +212,7 @@ fn forked_equals_pushed_genesis() -> Result<()> { // same original full chain. for block in chain.iter().skip(fork_at_count).cloned() { let block = - ContextuallyVerifiedBlock::with_block_and_spent_utxos(block, forked.unspent_utxos())?; + ContextuallyVerifiedBlock::with_block_and_spent_utxos(block, forked.unspent_utxos(), Default::default())?; forked = forked.push(block).expect("forked chain push is valid"); } From 3d00b812682737f19966543d1ac3011ae82d1bf5 Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 12 Nov 2024 02:48:15 -0500 Subject: [PATCH 05/35] Adds issued assets map to non-finalized chains --- zebra-chain/src/orchard_zsa/asset_state.rs | 47 +++++++++++++++---- zebra-consensus/src/block.rs | 2 +- zebra-state/src/arbitrary.rs | 2 +- zebra-state/src/request.rs | 20 ++------ zebra-state/src/service/check/issuance.rs | 4 +- .../zebra_db/block/tests/vectors.rs | 3 +- .../finalized_state/zebra_db/shielded.rs | 2 +- .../src/service/non_finalized_state/chain.rs | 40 +++++++++++++++- 8 files changed, 90 insertions(+), 30 deletions(-) diff --git a/zebra-chain/src/orchard_zsa/asset_state.rs b/zebra-chain/src/orchard_zsa/asset_state.rs index 8255da9cce7..7514b478a43 100644 --- a/zebra-chain/src/orchard_zsa/asset_state.rs +++ b/zebra-chain/src/orchard_zsa/asset_state.rs @@ -5,7 +5,7 @@ use std::{collections::HashMap, sync::Arc}; use orchard::issuance::IssueAction; pub use orchard::note::AssetBase; -use crate::block::Block; +use crate::transaction::Transaction; use super::BurnItem; @@ -30,9 +30,9 @@ pub struct AssetStateChange { } impl AssetState { - /// Updates and returns self with the provided [`AssetStateChange`] if the change is valid, or - /// returns None otherwise. - pub fn with_change(mut self, change: AssetStateChange) -> Option { + /// Updates and returns self with the provided [`AssetStateChange`] if + /// the change is valid, or returns None otherwise. + pub fn apply_change(mut self, change: AssetStateChange) -> Option { if self.is_finalized { return None; } @@ -41,6 +41,15 @@ impl AssetState { self.total_supply = self.total_supply.checked_add_signed(change.supply_change)?; Some(self) } + + /// Reverts the provided [`AssetStateChange`]. + pub fn revert_change(&mut self, change: AssetStateChange) { + self.is_finalized &= !change.is_finalized; + self.total_supply = self + .total_supply + .checked_add_signed(-change.supply_change) + .expect("reversions must not overflow"); + } } impl From> for IssuedAssets { @@ -108,6 +117,11 @@ impl IssuedAssets { Self(HashMap::new()) } + /// Returns an iterator of the inner HashMap. + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + fn update<'a>(&mut self, issued_assets: impl Iterator + 'a) { for (asset_base, asset_state) in issued_assets { self.0.insert(asset_base, asset_state); @@ -140,15 +154,15 @@ impl IssuedAssetsChange { } } - /// Accepts a reference to an [`Arc`]. + /// Accepts a slice of [`Arc`]s. /// /// Returns a tuple, ([`IssuedAssetsChange`], [`IssuedAssetsChange`]), where /// the first item is from burns and the second one is for issuance. - pub fn from_block(block: &Arc) -> (Self, Self) { + pub fn from_transactions(transactions: &[Arc]) -> (Self, Self) { let mut burn_change = Self::new(); let mut issuance_change = Self::new(); - for transaction in &block.transactions { + for transaction in transactions { burn_change.update(AssetStateChange::from_burns(transaction.orchard_burns())); issuance_change.update(AssetStateChange::from_issue_actions( transaction.orchard_issue_actions(), @@ -158,6 +172,23 @@ impl IssuedAssetsChange { (burn_change, issuance_change) } + /// Accepts a slice of [`Arc`]s. + /// + /// Returns an [`IssuedAssetsChange`] representing all of the changes to the issued assets + /// map that should be applied for the provided transactions. + pub fn combined_from_transactions(transactions: &[Arc]) -> Self { + let mut issued_assets_change = Self::new(); + + for transaction in transactions { + issued_assets_change.update(AssetStateChange::from_burns(transaction.orchard_burns())); + issued_assets_change.update(AssetStateChange::from_issue_actions( + transaction.orchard_issue_actions(), + )); + } + + issued_assets_change + } + /// Consumes self and accepts a closure for looking up previous asset states. /// /// Applies changes in self to the previous asset state. @@ -170,7 +201,7 @@ impl IssuedAssetsChange { ( asset_base, f(asset_base) - .with_change(change) + .apply_change(change) .expect("must be valid change"), ) })); diff --git a/zebra-consensus/src/block.rs b/zebra-consensus/src/block.rs index 46ca02e0a08..31fe5a24e9d 100644 --- a/zebra-consensus/src/block.rs +++ b/zebra-consensus/src/block.rs @@ -315,7 +315,7 @@ where let new_outputs = Arc::into_inner(known_utxos) .expect("all verification tasks using known_utxos are complete"); - let (burns, issuance) = IssuedAssetsChange::from_block(&block); + let (burns, issuance) = IssuedAssetsChange::from_transactions(&block.transactions); let prepared_block = zs::SemanticallyVerifiedBlock { block, hash, diff --git a/zebra-state/src/arbitrary.rs b/zebra-state/src/arbitrary.rs index 348e8fa6026..fb92a8fe7d7 100644 --- a/zebra-state/src/arbitrary.rs +++ b/zebra-state/src/arbitrary.rs @@ -32,7 +32,7 @@ impl Prepare for Arc { let transaction_hashes: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect(); let new_outputs = transparent::new_ordered_outputs_with_height(&block, height, &transaction_hashes); - let (burns, issuance) = IssuedAssetsChange::from_block(&block); + let (burns, issuance) = IssuedAssetsChange::from_transactions(&block.transactions); SemanticallyVerifiedBlock { block, diff --git a/zebra-state/src/request.rs b/zebra-state/src/request.rs index 71fc6049c50..92a9d162594 100644 --- a/zebra-state/src/request.rs +++ b/zebra-state/src/request.rs @@ -313,7 +313,7 @@ pub struct FinalizedBlock { #[derive(Clone, Debug, PartialEq, Eq)] pub enum IssuedAssetsOrChanges { /// A map of updated issued assets. - State(IssuedAssets), + Updated(IssuedAssets), /// A map of changes to apply to the issued assets map. Change(IssuedAssetsChange), @@ -499,7 +499,7 @@ impl SemanticallyVerifiedBlock { .expect("semantically verified block should have a coinbase height"); let transaction_hashes: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect(); let new_outputs = transparent::new_ordered_outputs(&block, &transaction_hashes); - let (burns, issuance) = IssuedAssetsChange::from_block(&block); + let (burns, issuance) = IssuedAssetsChange::from_transactions(&block.transactions); Self { block, @@ -537,7 +537,7 @@ impl From> for SemanticallyVerifiedBlock { .expect("semantically verified block should have a coinbase height"); let transaction_hashes: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect(); let new_outputs = transparent::new_ordered_outputs(&block, &transaction_hashes); - let (burns, issuance) = IssuedAssetsChange::from_block(&block); + let (burns, issuance) = IssuedAssetsChange::from_transactions(&block.transactions); Self { block, @@ -556,8 +556,6 @@ impl From> for SemanticallyVerifiedBlock { impl From for SemanticallyVerifiedBlock { fn from(valid: ContextuallyVerifiedBlock) -> Self { - let (burns, issuance) = IssuedAssetsChange::from_block(&valid.block); - Self { block: valid.block, hash: valid.hash, @@ -571,18 +569,13 @@ impl From for SemanticallyVerifiedBlock { .constrain::() .expect("deferred balance in a block must me non-negative"), ), - issued_assets_changes: IssuedAssetsOrChanges::BurnAndIssuanceChanges { - burns, - issuance, - }, + issued_assets_changes: IssuedAssetsOrChanges::Updated(valid.issued_assets), } } } impl From for SemanticallyVerifiedBlock { fn from(finalized: FinalizedBlock) -> Self { - let (burns, issuance) = IssuedAssetsChange::from_block(&finalized.block); - Self { block: finalized.block, hash: finalized.hash, @@ -590,10 +583,7 @@ impl From for SemanticallyVerifiedBlock { new_outputs: finalized.new_outputs, transaction_hashes: finalized.transaction_hashes, deferred_balance: finalized.deferred_balance, - issued_assets_changes: IssuedAssetsOrChanges::BurnAndIssuanceChanges { - burns, - issuance, - }, + issued_assets_changes: finalized.issued_assets, } } } diff --git a/zebra-state/src/service/check/issuance.rs b/zebra-state/src/service/check/issuance.rs index 610825dd2f2..5454e85290f 100644 --- a/zebra-state/src/service/check/issuance.rs +++ b/zebra-state/src/service/check/issuance.rs @@ -27,7 +27,7 @@ pub fn valid_burns_and_issuance( .issued_asset(&asset_base) .or_else(|| finalized_state.issued_asset(&asset_base)) .ok_or(ValidateContextError::InvalidBurn)? - .with_change(burn_change) + .apply_change(burn_change) .ok_or(ValidateContextError::InvalidBurn)?; issued_assets @@ -49,7 +49,7 @@ pub fn valid_burns_and_issuance( let _ = issued_assets.insert( asset_base, asset_state - .with_change(issuance_change) + .apply_change(issuance_change) .ok_or(ValidateContextError::InvalidIssuance)?, ); } diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs b/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs index 45555b969bf..7a98ea0e5dd 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs @@ -130,7 +130,8 @@ fn test_block_db_round_trip_with( .collect(); let new_outputs = new_ordered_outputs_with_height(&original_block, Height(0), &transaction_hashes); - let (burns, issuance) = IssuedAssetsChange::from_block(&original_block); + let (burns, issuance) = + IssuedAssetsChange::from_transactions(&original_block.transactions); CheckpointVerifiedBlock(SemanticallyVerifiedBlock { block: original_block.clone(), 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 02756265999..3fc2a57ef3e 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/shielded.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/shielded.rs @@ -520,7 +520,7 @@ impl DiskWriteBatch { let mut batch = zebra_db.issued_assets_cf().with_batch_for_writing(self); let updated_issued_assets = match issued_assets_or_changes.clone().combine() { - IssuedAssetsOrChanges::State(issued_assets) => issued_assets, + IssuedAssetsOrChanges::Updated(issued_assets) => issued_assets, IssuedAssetsOrChanges::Change(issued_assets_change) => issued_assets_change .apply_with(|asset_base| zebra_db.issued_asset(&asset_base).unwrap_or_default()), IssuedAssetsOrChanges::BurnAndIssuanceChanges { .. } => { diff --git a/zebra-state/src/service/non_finalized_state/chain.rs b/zebra-state/src/service/non_finalized_state/chain.rs index 31ee8c027ba..403b29999f2 100644 --- a/zebra-state/src/service/non_finalized_state/chain.rs +++ b/zebra-state/src/service/non_finalized_state/chain.rs @@ -16,7 +16,7 @@ use zebra_chain::{ block::{self, Height}, history_tree::HistoryTree, orchard, - orchard_zsa::{AssetBase, AssetState}, + orchard_zsa::{AssetBase, AssetState, IssuedAssets, IssuedAssetsChange}, parallel::tree::NoteCommitmentTrees, parameters::Network, primitives::Groth16Proof, @@ -952,6 +952,36 @@ impl Chain { self.issued_assets.get(asset_base).cloned() } + /// Remove the History tree index at `height`. + fn revert_issued_assets( + &mut self, + position: RevertPosition, + issued_assets: &IssuedAssets, + transactions: &[Arc], + ) { + if position == RevertPosition::Root { + trace!(?position, "removing unmodified issued assets"); + for (asset_base, &asset_state) in issued_assets.iter() { + if self + .issued_asset(asset_base) + .expect("issued assets for chain should include those in all blocks") + == asset_state + { + self.issued_assets.remove(asset_base); + } + } + } else { + trace!(?position, "reverting changes to issued assets"); + for (asset_base, change) in IssuedAssetsChange::combined_from_transactions(transactions) + { + self.issued_assets + .entry(asset_base) + .or_default() + .revert_change(change); + } + } + } + /// Adds the Orchard `tree` to the tree and anchor indexes at `height`. /// /// `height` can be either: @@ -1454,6 +1484,9 @@ impl Chain { self.add_history_tree(height, history_tree); + self.issued_assets + .extend(contextually_valid.issued_assets.clone()); + Ok(()) } @@ -1682,6 +1715,7 @@ impl UpdateWith for Chain { spent_outputs, transaction_hashes, chain_value_pool_change, + issued_assets, ) = ( contextually_valid.block.as_ref(), contextually_valid.hash, @@ -1690,6 +1724,7 @@ impl UpdateWith for Chain { &contextually_valid.spent_outputs, &contextually_valid.transaction_hashes, &contextually_valid.chain_value_pool_change, + &contextually_valid.issued_assets, ); // remove the blocks hash from `height_by_hash` @@ -1788,6 +1823,9 @@ impl UpdateWith for Chain { // TODO: move this to the history tree UpdateWith.revert...()? self.remove_history_tree(position, height); + // revert the issued assets map, if needed + self.revert_issued_assets(position, issued_assets, &block.transactions); + // revert the chain value pool balances, if needed self.revert_chain_with(chain_value_pool_change, position); } From 2daf84f6a6a74b28048f5f80327d59b2f58e2d51 Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 12 Nov 2024 03:24:04 -0500 Subject: [PATCH 06/35] adds new column family to list of state column families --- zebra-chain/src/orchard_zsa/asset_state.rs | 5 ++++- zebra-state/src/service/finalized_state.rs | 1 + .../disk_format/tests/snapshots/column_family_names.snap | 1 + .../tests/snapshots/empty_column_families@mainnet_0.snap | 1 + .../tests/snapshots/empty_column_families@mainnet_1.snap | 1 + .../tests/snapshots/empty_column_families@mainnet_2.snap | 1 + .../tests/snapshots/empty_column_families@no_blocks.snap | 1 + .../tests/snapshots/empty_column_families@testnet_0.snap | 1 + .../tests/snapshots/empty_column_families@testnet_1.snap | 1 + .../tests/snapshots/empty_column_families@testnet_2.snap | 1 + 10 files changed, 13 insertions(+), 1 deletion(-) diff --git a/zebra-chain/src/orchard_zsa/asset_state.rs b/zebra-chain/src/orchard_zsa/asset_state.rs index 7514b478a43..e7413ad7604 100644 --- a/zebra-chain/src/orchard_zsa/asset_state.rs +++ b/zebra-chain/src/orchard_zsa/asset_state.rs @@ -20,7 +20,10 @@ pub struct AssetState { } /// A change to apply to the issued assets map. -// TODO: Reference ZIP +// TODO: +// - Reference ZIP +// - Make this an enum of _either_ a finalization _or_ a supply change +// (applying the finalize flag for each issuance note will cause unexpected panics). #[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct AssetStateChange { /// Whether the asset should be finalized such that no more of it can be issued. diff --git a/zebra-state/src/service/finalized_state.rs b/zebra-state/src/service/finalized_state.rs index f8c9bade5c1..94328d9e51f 100644 --- a/zebra-state/src/service/finalized_state.rs +++ b/zebra-state/src/service/finalized_state.rs @@ -91,6 +91,7 @@ pub const STATE_COLUMN_FAMILIES_IN_CODE: &[&str] = &[ "orchard_anchors", "orchard_note_commitment_tree", "orchard_note_commitment_subtree", + "orchard_issued_assets", // Chain "history_tree", "tip_chain_value_pool", diff --git a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/column_family_names.snap b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/column_family_names.snap index d37e037cac7..33f1c76717b 100644 --- a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/column_family_names.snap +++ b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/column_family_names.snap @@ -12,6 +12,7 @@ expression: cf_names "height_by_hash", "history_tree", "orchard_anchors", + "orchard_issued_assets", "orchard_note_commitment_subtree", "orchard_note_commitment_tree", "orchard_nullifiers", diff --git a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@mainnet_0.snap b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@mainnet_0.snap index 3c333a9fc43..abd4ae001ec 100644 --- a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@mainnet_0.snap +++ b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@mainnet_0.snap @@ -5,6 +5,7 @@ expression: empty_column_families [ "balance_by_transparent_addr: no entries", "history_tree: no entries", + "orchard_issued_assets: no entries", "orchard_note_commitment_subtree: no entries", "orchard_nullifiers: no entries", "sapling_note_commitment_subtree: no entries", diff --git a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@mainnet_1.snap b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@mainnet_1.snap index cb8ac5f6aed..8b114ddce4d 100644 --- a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@mainnet_1.snap +++ b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@mainnet_1.snap @@ -4,6 +4,7 @@ expression: empty_column_families --- [ "history_tree: no entries", + "orchard_issued_assets: no entries", "orchard_note_commitment_subtree: no entries", "orchard_nullifiers: no entries", "sapling_note_commitment_subtree: no entries", diff --git a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@mainnet_2.snap b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@mainnet_2.snap index cb8ac5f6aed..8b114ddce4d 100644 --- a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@mainnet_2.snap +++ b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@mainnet_2.snap @@ -4,6 +4,7 @@ expression: empty_column_families --- [ "history_tree: no entries", + "orchard_issued_assets: no entries", "orchard_note_commitment_subtree: no entries", "orchard_nullifiers: no entries", "sapling_note_commitment_subtree: no entries", diff --git a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@no_blocks.snap b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@no_blocks.snap index a2abce2083b..2d119139d26 100644 --- a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@no_blocks.snap +++ b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@no_blocks.snap @@ -11,6 +11,7 @@ expression: empty_column_families "height_by_hash: no entries", "history_tree: no entries", "orchard_anchors: no entries", + "orchard_issued_assets: no entries", "orchard_note_commitment_subtree: no entries", "orchard_note_commitment_tree: no entries", "orchard_nullifiers: no entries", diff --git a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@testnet_0.snap b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@testnet_0.snap index 3c333a9fc43..abd4ae001ec 100644 --- a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@testnet_0.snap +++ b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@testnet_0.snap @@ -5,6 +5,7 @@ expression: empty_column_families [ "balance_by_transparent_addr: no entries", "history_tree: no entries", + "orchard_issued_assets: no entries", "orchard_note_commitment_subtree: no entries", "orchard_nullifiers: no entries", "sapling_note_commitment_subtree: no entries", diff --git a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@testnet_1.snap b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@testnet_1.snap index cb8ac5f6aed..8b114ddce4d 100644 --- a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@testnet_1.snap +++ b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@testnet_1.snap @@ -4,6 +4,7 @@ expression: empty_column_families --- [ "history_tree: no entries", + "orchard_issued_assets: no entries", "orchard_note_commitment_subtree: no entries", "orchard_nullifiers: no entries", "sapling_note_commitment_subtree: no entries", diff --git a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@testnet_2.snap b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@testnet_2.snap index cb8ac5f6aed..8b114ddce4d 100644 --- a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@testnet_2.snap +++ b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/empty_column_families@testnet_2.snap @@ -4,6 +4,7 @@ expression: empty_column_families --- [ "history_tree: no entries", + "orchard_issued_assets: no entries", "orchard_note_commitment_subtree: no entries", "orchard_nullifiers: no entries", "sapling_note_commitment_subtree: no entries", From c6c099b20cdf67947b3d0b84567872ad6fe8a3f1 Mon Sep 17 00:00:00 2001 From: Arya Date: Wed, 13 Nov 2024 21:45:23 -0500 Subject: [PATCH 07/35] Updates AssetState, AssetStateChange, IssuedAssetsOrChange, & SemanticallyVerifiedBlock types, updates `IssuedAssetsChange::from_transactions()` method return type --- zebra-chain/src/orchard_zsa/asset_state.rs | 218 ++++++++++++------ zebra-consensus/src/block.rs | 11 +- zebra-consensus/src/checkpoint.rs | 5 +- zebra-consensus/src/error.rs | 3 + zebra-state/src/arbitrary.rs | 12 +- zebra-state/src/lib.rs | 2 +- zebra-state/src/request.rs | 97 ++++---- zebra-state/src/service/chain_tip.rs | 2 +- zebra-state/src/service/check/issuance.rs | 51 ++-- .../finalized_state/disk_format/shielded.rs | 2 +- .../zebra_db/block/tests/vectors.rs | 12 +- .../finalized_state/zebra_db/shielded.rs | 13 +- .../src/service/non_finalized_state/chain.rs | 3 +- 13 files changed, 242 insertions(+), 189 deletions(-) diff --git a/zebra-chain/src/orchard_zsa/asset_state.rs b/zebra-chain/src/orchard_zsa/asset_state.rs index e7413ad7604..d355c1d0825 100644 --- a/zebra-chain/src/orchard_zsa/asset_state.rs +++ b/zebra-chain/src/orchard_zsa/asset_state.rs @@ -1,6 +1,9 @@ //! Defines and implements the issued asset state types -use std::{collections::HashMap, sync::Arc}; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; use orchard::issuance::IssueAction; pub use orchard::note::AssetBase; @@ -16,42 +19,108 @@ pub struct AssetState { pub is_finalized: bool, /// The circulating supply that has been issued for an asset. - pub total_supply: u128, + pub total_supply: u64, } /// A change to apply to the issued assets map. -// TODO: -// - Reference ZIP -// - Make this an enum of _either_ a finalization _or_ a supply change -// (applying the finalize flag for each issuance note will cause unexpected panics). +// TODO: Reference ZIP #[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct AssetStateChange { /// Whether the asset should be finalized such that no more of it can be issued. pub is_finalized: bool, - /// The change in supply from newly issued assets or burned assets. - pub supply_change: i128, + /// The change in supply from newly issued assets or burned assets, if any. + pub supply_change: SupplyChange, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +/// An asset supply change to apply to the issued assets map. +pub enum SupplyChange { + Issuance(u64), + Burn(u64), +} + +impl Default for SupplyChange { + fn default() -> Self { + Self::Issuance(0) + } +} + +impl SupplyChange { + fn apply_to(self, total_supply: u64) -> Option { + match self { + SupplyChange::Issuance(amount) => total_supply.checked_add(amount), + SupplyChange::Burn(amount) => total_supply.checked_sub(amount), + } + } + + fn as_i128(self) -> i128 { + match self { + SupplyChange::Issuance(amount) => i128::from(amount), + SupplyChange::Burn(amount) => -i128::from(amount), + } + } + + fn add(&mut self, rhs: Self) -> bool { + if let Some(result) = self + .as_i128() + .checked_add(rhs.as_i128()) + .and_then(|signed| match signed { + 0.. => signed.try_into().ok().map(Self::Issuance), + ..0 => signed.try_into().ok().map(Self::Burn), + }) + { + *self = result; + true + } else { + false + } + } +} + +impl std::ops::Neg for SupplyChange { + type Output = Self; + + fn neg(self) -> Self::Output { + match self { + Self::Issuance(amount) => Self::Burn(amount), + Self::Burn(amount) => Self::Issuance(amount), + } + } } impl AssetState { /// Updates and returns self with the provided [`AssetStateChange`] if /// the change is valid, or returns None otherwise. - pub fn apply_change(mut self, change: AssetStateChange) -> Option { + pub fn apply_change(self, change: AssetStateChange) -> Option { + self.apply_finalization(change.is_finalized)? + .apply_supply_change(change.supply_change) + } + + fn apply_finalization(mut self, is_finalized: bool) -> Option { if self.is_finalized { - return None; + None + } else { + self.is_finalized = is_finalized; + Some(self) } + } - self.is_finalized |= change.is_finalized; - self.total_supply = self.total_supply.checked_add_signed(change.supply_change)?; + fn apply_supply_change(mut self, supply_change: SupplyChange) -> Option { + self.total_supply = supply_change.apply_to(self.total_supply)?; Some(self) } /// Reverts the provided [`AssetStateChange`]. pub fn revert_change(&mut self, change: AssetStateChange) { - self.is_finalized &= !change.is_finalized; - self.total_supply = self - .total_supply - .checked_add_signed(-change.supply_change) - .expect("reversions must not overflow"); + *self = self + .revert_finalization(change.is_finalized) + .apply_supply_change(-change.supply_change) + .expect("reverted change should be validated"); + } + + fn revert_finalization(mut self, is_finalized: bool) -> Self { + self.is_finalized &= !is_finalized; + self } } @@ -62,50 +131,79 @@ impl From> for IssuedAssets { } impl AssetStateChange { - fn from_note(is_finalized: bool, note: orchard::Note) -> (AssetBase, Self) { + fn new( + asset_base: AssetBase, + supply_change: SupplyChange, + is_finalized: bool, + ) -> (AssetBase, Self) { ( - note.asset(), + asset_base, Self { is_finalized, - supply_change: note.value().inner().into(), + supply_change, }, ) } - fn from_notes( - is_finalized: bool, - notes: &[orchard::Note], - ) -> impl Iterator + '_ { - notes - .iter() - .map(move |note| Self::from_note(is_finalized, *note)) + fn from_transaction(tx: &Arc) -> impl Iterator + '_ { + Self::from_burns(tx.orchard_burns()) + .chain(Self::from_issue_actions(tx.orchard_issue_actions())) } fn from_issue_actions<'a>( actions: impl Iterator + 'a, ) -> impl Iterator + 'a { - actions.flat_map(|action| Self::from_notes(action.is_finalized(), action.notes())) + actions.flat_map(Self::from_issue_action) } - fn from_burn(burn: &BurnItem) -> (AssetBase, Self) { - ( - burn.asset(), - Self { - is_finalized: false, - supply_change: -i128::from(burn.amount()), - }, + fn from_issue_action(action: &IssueAction) -> impl Iterator + '_ { + let supply_changes = Self::from_notes(action.notes()); + let finalize_changes = action + .is_finalized() + .then(|| { + action + .notes() + .iter() + .map(orchard::Note::asset) + .collect::>() + }) + .unwrap_or_default() + .into_iter() + .map(|asset_base| Self::new(asset_base, SupplyChange::Issuance(0), true)); + + supply_changes.chain(finalize_changes) + } + + fn from_notes(notes: &[orchard::Note]) -> impl Iterator + '_ { + notes.iter().copied().map(Self::from_note) + } + + fn from_note(note: orchard::Note) -> (AssetBase, Self) { + Self::new( + note.asset(), + SupplyChange::Issuance(note.value().inner()), + false, ) } fn from_burns(burns: &[BurnItem]) -> impl Iterator + '_ { burns.iter().map(Self::from_burn) } -} -impl std::ops::AddAssign for AssetStateChange { - fn add_assign(&mut self, rhs: Self) { - self.is_finalized |= rhs.is_finalized; - self.supply_change += rhs.supply_change; + fn from_burn(burn: &BurnItem) -> (AssetBase, Self) { + Self::new(burn.asset(), SupplyChange::Burn(burn.amount()), false) + } + + /// Updates and returns self with the provided [`AssetStateChange`] if + /// the change is valid, or returns None otherwise. + pub fn apply_change(&mut self, change: AssetStateChange) -> bool { + self.is_finalized |= change.is_finalized; + self.supply_change.add(change.supply_change) + } + + /// Returns true if the AssetStateChange is for an asset burn. + pub fn is_burn(&self) -> bool { + matches!(self.supply_change, SupplyChange::Burn(_)) } } @@ -143,7 +241,7 @@ impl IntoIterator for IssuedAssets { } /// A map of changes to apply to the issued assets map. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct IssuedAssetsChange(HashMap); impl IssuedAssetsChange { @@ -151,45 +249,33 @@ impl IssuedAssetsChange { Self(HashMap::new()) } - fn update<'a>(&mut self, changes: impl Iterator + 'a) { + fn update<'a>( + &mut self, + changes: impl Iterator + 'a, + ) -> bool { for (asset_base, change) in changes { - *self.0.entry(asset_base).or_default() += change; - } - } - - /// Accepts a slice of [`Arc`]s. - /// - /// Returns a tuple, ([`IssuedAssetsChange`], [`IssuedAssetsChange`]), where - /// the first item is from burns and the second one is for issuance. - pub fn from_transactions(transactions: &[Arc]) -> (Self, Self) { - let mut burn_change = Self::new(); - let mut issuance_change = Self::new(); - - for transaction in transactions { - burn_change.update(AssetStateChange::from_burns(transaction.orchard_burns())); - issuance_change.update(AssetStateChange::from_issue_actions( - transaction.orchard_issue_actions(), - )); + if !self.0.entry(asset_base).or_default().apply_change(change) { + return false; + } } - (burn_change, issuance_change) + true } /// Accepts a slice of [`Arc`]s. /// /// Returns an [`IssuedAssetsChange`] representing all of the changes to the issued assets /// map that should be applied for the provided transactions. - pub fn combined_from_transactions(transactions: &[Arc]) -> Self { + pub fn from_transactions(transactions: &[Arc]) -> Option { let mut issued_assets_change = Self::new(); for transaction in transactions { - issued_assets_change.update(AssetStateChange::from_burns(transaction.orchard_burns())); - issued_assets_change.update(AssetStateChange::from_issue_actions( - transaction.orchard_issue_actions(), - )); + if !issued_assets_change.update(AssetStateChange::from_transaction(transaction)) { + return None; + } } - issued_assets_change + Some(issued_assets_change) } /// Consumes self and accepts a closure for looking up previous asset states. diff --git a/zebra-consensus/src/block.rs b/zebra-consensus/src/block.rs index 31fe5a24e9d..6728bd9be66 100644 --- a/zebra-consensus/src/block.rs +++ b/zebra-consensus/src/block.rs @@ -29,7 +29,7 @@ use zebra_chain::{ transparent, work::equihash, }; -use zebra_state::{self as zs, IssuedAssetsOrChanges}; +use zebra_state as zs; use crate::{error::*, transaction as tx, BoxError}; @@ -315,7 +315,9 @@ where let new_outputs = Arc::into_inner(known_utxos) .expect("all verification tasks using known_utxos are complete"); - let (burns, issuance) = IssuedAssetsChange::from_transactions(&block.transactions); + let issued_assets_change = IssuedAssetsChange::from_transactions(&block.transactions) + .ok_or(TransactionError::InvalidAssetIssuanceOrBurn)?; + let prepared_block = zs::SemanticallyVerifiedBlock { block, hash, @@ -323,10 +325,7 @@ where new_outputs, transaction_hashes, deferred_balance: Some(expected_deferred_amount), - issued_assets_changes: IssuedAssetsOrChanges::BurnAndIssuanceChanges { - burns, - issuance, - }, + issued_assets_change: Some(issued_assets_change), }; // Return early for proposal requests when getblocktemplate-rpcs feature is enabled diff --git a/zebra-consensus/src/checkpoint.rs b/zebra-consensus/src/checkpoint.rs index 039ea6e33e3..f6520ba5564 100644 --- a/zebra-consensus/src/checkpoint.rs +++ b/zebra-consensus/src/checkpoint.rs @@ -42,7 +42,7 @@ use crate::{ Progress::{self, *}, TargetHeight::{self, *}, }, - error::{BlockError, SubsidyError}, + error::{BlockError, SubsidyError, TransactionError}, funding_stream_values, BoxError, ParameterCheckpoint as _, }; @@ -619,7 +619,8 @@ where }; // don't do precalculation until the block passes basic difficulty checks - let block = CheckpointVerifiedBlock::new(block, Some(hash), expected_deferred_amount); + let block = CheckpointVerifiedBlock::new(block, Some(hash), expected_deferred_amount) + .ok_or_else(|| VerifyBlockError::from(TransactionError::InvalidAssetIssuanceOrBurn))?; crate::block::check::merkle_root_validity( &self.network, diff --git a/zebra-consensus/src/error.rs b/zebra-consensus/src/error.rs index 8fe14c62d52..9aa41103910 100644 --- a/zebra-consensus/src/error.rs +++ b/zebra-consensus/src/error.rs @@ -239,6 +239,9 @@ pub enum TransactionError { #[error("failed to verify ZIP-317 transaction rules, transaction was not inserted to mempool")] #[cfg_attr(any(test, feature = "proptest-impl"), proptest(skip))] Zip317(#[from] zebra_chain::transaction::zip317::Error), + + #[error("failed to validate asset issuance and/or burns")] + InvalidAssetIssuanceOrBurn, } impl From for TransactionError { diff --git a/zebra-state/src/arbitrary.rs b/zebra-state/src/arbitrary.rs index fb92a8fe7d7..c5ec8ef3a82 100644 --- a/zebra-state/src/arbitrary.rs +++ b/zebra-state/src/arbitrary.rs @@ -5,15 +5,13 @@ use std::sync::Arc; use zebra_chain::{ amount::Amount, block::{self, Block}, - orchard_zsa::IssuedAssetsChange, transaction::Transaction, transparent, value_balance::ValueBalance, }; use crate::{ - request::{ContextuallyVerifiedBlock, IssuedAssetsOrChanges}, - service::chain_tip::ChainTipBlock, + request::ContextuallyVerifiedBlock, service::chain_tip::ChainTipBlock, SemanticallyVerifiedBlock, }; @@ -32,7 +30,6 @@ impl Prepare for Arc { let transaction_hashes: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect(); let new_outputs = transparent::new_ordered_outputs_with_height(&block, height, &transaction_hashes); - let (burns, issuance) = IssuedAssetsChange::from_transactions(&block.transactions); SemanticallyVerifiedBlock { block, @@ -41,10 +38,7 @@ impl Prepare for Arc { new_outputs, transaction_hashes, deferred_balance: None, - issued_assets_changes: IssuedAssetsOrChanges::BurnAndIssuanceChanges { - burns, - issuance, - }, + issued_assets_change: None, } } } @@ -123,7 +117,7 @@ impl ContextuallyVerifiedBlock { new_outputs, transaction_hashes, deferred_balance: _, - issued_assets_changes: _, + issued_assets_change: _, } = block.into(); Self { diff --git a/zebra-state/src/lib.rs b/zebra-state/src/lib.rs index 58010fb648e..7cfc8304bdd 100644 --- a/zebra-state/src/lib.rs +++ b/zebra-state/src/lib.rs @@ -42,7 +42,7 @@ pub use error::{ ValidateContextError, }; pub use request::{ - CheckpointVerifiedBlock, HashOrHeight, IssuedAssetsOrChanges, ReadRequest, Request, + CheckpointVerifiedBlock, HashOrHeight, IssuedAssetsOrChange, ReadRequest, Request, SemanticallyVerifiedBlock, }; pub use response::{KnownBlock, MinedTx, ReadResponse, Response}; diff --git a/zebra-state/src/request.rs b/zebra-state/src/request.rs index 92a9d162594..3c0e3834278 100644 --- a/zebra-state/src/request.rs +++ b/zebra-state/src/request.rs @@ -166,7 +166,7 @@ pub struct SemanticallyVerifiedBlock { pub deferred_balance: Option>, /// A map of burns to be applied to the issued assets map. // TODO: Reference ZIP. - pub issued_assets_changes: IssuedAssetsOrChanges, + pub issued_assets_change: Option, } /// A block ready to be committed directly to the finalized state with @@ -304,51 +304,47 @@ pub struct FinalizedBlock { /// Either changes to be applied to the previous `issued_assets` map for the finalized tip, or /// updates asset states to be inserted into the finalized state, replacing the previous /// asset states for those asset bases. - pub issued_assets: IssuedAssetsOrChanges, + pub issued_assets: IssuedAssetsOrChange, } /// Either changes to be applied to the previous `issued_assets` map for the finalized tip, or /// updates asset states to be inserted into the finalized state, replacing the previous /// asset states for those asset bases. #[derive(Clone, Debug, PartialEq, Eq)] -pub enum IssuedAssetsOrChanges { +pub enum IssuedAssetsOrChange { /// A map of updated issued assets. Updated(IssuedAssets), /// A map of changes to apply to the issued assets map. Change(IssuedAssetsChange), - - /// A map of changes from burns and issuance to apply to the issued assets map. - BurnAndIssuanceChanges { - /// A map of changes from burns to apply to the issued assets map. - burns: IssuedAssetsChange, - /// A map of changes from issuance to apply to the issued assets map. - issuance: IssuedAssetsChange, - }, } -impl IssuedAssetsOrChanges { - /// Combines fields in the `BurnAndIssuanceChanges` variant then returns a `Change` variant, or - /// returns self unmodified. - pub fn combine(self) -> Self { - let Self::BurnAndIssuanceChanges { burns, issuance } = self else { - return self; - }; - - Self::Change(burns + issuance) +impl From for IssuedAssetsOrChange { + fn from(change: IssuedAssetsChange) -> Self { + Self::Change(change) } } -impl From for IssuedAssetsOrChanges { - fn from(change: IssuedAssetsChange) -> Self { - Self::Change(change) +impl From for IssuedAssetsOrChange { + fn from(updated_issued_assets: IssuedAssets) -> Self { + Self::Updated(updated_issued_assets) } } impl FinalizedBlock { /// Constructs [`FinalizedBlock`] from [`CheckpointVerifiedBlock`] and its [`Treestate`]. pub fn from_checkpoint_verified(block: CheckpointVerifiedBlock, treestate: Treestate) -> Self { - Self::from_semantically_verified(SemanticallyVerifiedBlock::from(block), treestate) + let issued_assets = block + .issued_assets_change + .clone() + .expect("checkpoint verified block should have issued assets change") + .into(); + + Self::from_semantically_verified( + SemanticallyVerifiedBlock::from(block), + treestate, + issued_assets, + ) } /// Constructs [`FinalizedBlock`] from [`ContextuallyVerifiedBlock`] and its [`Treestate`]. @@ -356,11 +352,20 @@ impl FinalizedBlock { block: ContextuallyVerifiedBlock, treestate: Treestate, ) -> Self { - Self::from_semantically_verified(SemanticallyVerifiedBlock::from(block), treestate) + let issued_assets = block.issued_assets.clone().into(); + Self::from_semantically_verified( + SemanticallyVerifiedBlock::from(block), + treestate, + issued_assets, + ) } /// Constructs [`FinalizedBlock`] from [`SemanticallyVerifiedBlock`] and its [`Treestate`]. - fn from_semantically_verified(block: SemanticallyVerifiedBlock, treestate: Treestate) -> Self { + fn from_semantically_verified( + block: SemanticallyVerifiedBlock, + treestate: Treestate, + issued_assets: IssuedAssetsOrChange, + ) -> Self { Self { block: block.block, hash: block.hash, @@ -369,7 +374,7 @@ impl FinalizedBlock { transaction_hashes: block.transaction_hashes, treestate, deferred_balance: block.deferred_balance, - issued_assets: block.issued_assets_changes, + issued_assets, } } } @@ -444,7 +449,7 @@ impl ContextuallyVerifiedBlock { new_outputs, transaction_hashes, deferred_balance, - issued_assets_changes: _, + issued_assets_change: _, } = semantically_verified; // This is redundant for the non-finalized state, @@ -476,11 +481,14 @@ impl CheckpointVerifiedBlock { block: Arc, hash: Option, deferred_balance: Option>, - ) -> Self { + ) -> Option { + let issued_assets_change = IssuedAssetsChange::from_transactions(&block.transactions)?; let mut block = Self::with_hash(block.clone(), hash.unwrap_or(block.hash())); block.deferred_balance = deferred_balance; - block + block.issued_assets_change = Some(issued_assets_change); + Some(block) } + /// Creates a block that's ready to be committed to the finalized state, /// using a precalculated [`block::Hash`]. /// @@ -499,7 +507,6 @@ impl SemanticallyVerifiedBlock { .expect("semantically verified block should have a coinbase height"); let transaction_hashes: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect(); let new_outputs = transparent::new_ordered_outputs(&block, &transaction_hashes); - let (burns, issuance) = IssuedAssetsChange::from_transactions(&block.transactions); Self { block, @@ -508,11 +515,7 @@ impl SemanticallyVerifiedBlock { new_outputs, transaction_hashes, deferred_balance: None, - issued_assets_changes: IssuedAssetsOrChanges::BurnAndIssuanceChanges { - burns, - issuance, - } - .combine(), + issued_assets_change: None, } } @@ -537,7 +540,6 @@ impl From> for SemanticallyVerifiedBlock { .expect("semantically verified block should have a coinbase height"); let transaction_hashes: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect(); let new_outputs = transparent::new_ordered_outputs(&block, &transaction_hashes); - let (burns, issuance) = IssuedAssetsChange::from_transactions(&block.transactions); Self { block, @@ -546,10 +548,7 @@ impl From> for SemanticallyVerifiedBlock { new_outputs, transaction_hashes, deferred_balance: None, - issued_assets_changes: IssuedAssetsOrChanges::BurnAndIssuanceChanges { - burns, - issuance, - }, + issued_assets_change: None, } } } @@ -569,21 +568,7 @@ impl From for SemanticallyVerifiedBlock { .constrain::() .expect("deferred balance in a block must me non-negative"), ), - issued_assets_changes: IssuedAssetsOrChanges::Updated(valid.issued_assets), - } - } -} - -impl From for SemanticallyVerifiedBlock { - fn from(finalized: FinalizedBlock) -> Self { - Self { - block: finalized.block, - hash: finalized.hash, - height: finalized.height, - new_outputs: finalized.new_outputs, - transaction_hashes: finalized.transaction_hashes, - deferred_balance: finalized.deferred_balance, - issued_assets_changes: finalized.issued_assets, + issued_assets_change: None, } } } diff --git a/zebra-state/src/service/chain_tip.rs b/zebra-state/src/service/chain_tip.rs index 8a0ed517766..95306b54282 100644 --- a/zebra-state/src/service/chain_tip.rs +++ b/zebra-state/src/service/chain_tip.rs @@ -116,7 +116,7 @@ impl From for ChainTipBlock { new_outputs: _, transaction_hashes, deferred_balance: _, - issued_assets_changes: _, + issued_assets_change: _, } = prepared; Self { diff --git a/zebra-state/src/service/check/issuance.rs b/zebra-state/src/service/check/issuance.rs index 5454e85290f..daf7635e68a 100644 --- a/zebra-state/src/service/check/issuance.rs +++ b/zebra-state/src/service/check/issuance.rs @@ -4,7 +4,7 @@ use std::{collections::HashMap, sync::Arc}; use zebra_chain::orchard_zsa::IssuedAssets; -use crate::{IssuedAssetsOrChanges, SemanticallyVerifiedBlock, ValidateContextError, ZebraDb}; +use crate::{SemanticallyVerifiedBlock, ValidateContextError, ZebraDb}; use super::Chain; @@ -13,45 +13,34 @@ pub fn valid_burns_and_issuance( parent_chain: &Arc, semantically_verified: &SemanticallyVerifiedBlock, ) -> Result { - let IssuedAssetsOrChanges::BurnAndIssuanceChanges { burns, issuance } = - semantically_verified.issued_assets_changes.clone() - else { - panic!("unexpected variant in semantically verified block") + let Some(issued_assets_change) = semantically_verified.issued_assets_change.clone() else { + return Ok(IssuedAssets::default()); }; let mut issued_assets = HashMap::new(); - for (asset_base, burn_change) in burns.clone() { - // TODO: Move this to a read fn. - let updated_asset_state = parent_chain - .issued_asset(&asset_base) - .or_else(|| finalized_state.issued_asset(&asset_base)) - .ok_or(ValidateContextError::InvalidBurn)? - .apply_change(burn_change) - .ok_or(ValidateContextError::InvalidBurn)?; - - issued_assets - .insert(asset_base, updated_asset_state) - .expect("transactions must have only one burn item per asset base"); - } - - for (asset_base, issuance_change) in issuance.clone() { - // TODO: Move this to a read fn. - let Some(asset_state) = issued_assets + for (asset_base, change) in issued_assets_change { + let asset_state = issued_assets .get(&asset_base) .copied() .or_else(|| parent_chain.issued_asset(&asset_base)) - .or_else(|| finalized_state.issued_asset(&asset_base)) - else { - continue; - }; + .or_else(|| finalized_state.issued_asset(&asset_base)); - let _ = issued_assets.insert( - asset_base, + let updated_asset_state = if change.is_burn() { asset_state - .apply_change(issuance_change) - .ok_or(ValidateContextError::InvalidIssuance)?, - ); + .ok_or(ValidateContextError::InvalidBurn)? + .apply_change(change) + .ok_or(ValidateContextError::InvalidBurn)? + } else { + asset_state + .unwrap_or_default() + .apply_change(change) + .ok_or(ValidateContextError::InvalidIssuance)? + }; + + issued_assets + .insert(asset_base, updated_asset_state) + .expect("transactions must have only one burn item per asset base"); } Ok(issued_assets.into()) 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 953815cae4c..cb2844d4c08 100644 --- a/zebra-state/src/service/finalized_state/disk_format/shielded.rs +++ b/zebra-state/src/service/finalized_state/disk_format/shielded.rs @@ -233,7 +233,7 @@ impl FromDisk for AssetState { Self { is_finalized: is_finalized_byte != 0, - total_supply: u64::from_be_bytes(total_supply_bytes).into(), + total_supply: u64::from_be_bytes(total_supply_bytes), } } } diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs b/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs index 7a98ea0e5dd..7b35af87a75 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs @@ -29,7 +29,7 @@ use zebra_test::vectors::{MAINNET_BLOCKS, TESTNET_BLOCKS}; use crate::{ constants::{state_database_format_version_in_code, STATE_DATABASE_KIND}, - request::{FinalizedBlock, IssuedAssetsOrChanges, Treestate}, + request::{FinalizedBlock, Treestate}, service::finalized_state::{disk_db::DiskWriteBatch, ZebraDb, STATE_COLUMN_FAMILIES_IN_CODE}, CheckpointVerifiedBlock, Config, SemanticallyVerifiedBlock, }; @@ -130,8 +130,9 @@ fn test_block_db_round_trip_with( .collect(); let new_outputs = new_ordered_outputs_with_height(&original_block, Height(0), &transaction_hashes); - let (burns, issuance) = - IssuedAssetsChange::from_transactions(&original_block.transactions); + let issued_assets_change = + IssuedAssetsChange::from_transactions(&original_block.transactions) + .expect("issued assets should be valid"); CheckpointVerifiedBlock(SemanticallyVerifiedBlock { block: original_block.clone(), @@ -140,10 +141,7 @@ fn test_block_db_round_trip_with( new_outputs, transaction_hashes, deferred_balance: None, - issued_assets_changes: IssuedAssetsOrChanges::BurnAndIssuanceChanges { - burns, - issuance, - }, + issued_assets_change: Some(issued_assets_change), }) }; 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 3fc2a57ef3e..1ca7e9cd3dc 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/shielded.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/shielded.rs @@ -34,7 +34,7 @@ use crate::{ disk_format::RawBytes, zebra_db::ZebraDb, }, - BoxError, IssuedAssetsOrChanges, TypedColumnFamily, + BoxError, IssuedAssetsOrChange, TypedColumnFamily, }; // Doc-only items @@ -515,17 +515,14 @@ impl DiskWriteBatch { pub fn prepare_issued_assets_batch( &mut self, zebra_db: &ZebraDb, - issued_assets_or_changes: &IssuedAssetsOrChanges, + issued_assets_or_changes: &IssuedAssetsOrChange, ) -> Result<(), BoxError> { let mut batch = zebra_db.issued_assets_cf().with_batch_for_writing(self); - let updated_issued_assets = match issued_assets_or_changes.clone().combine() { - IssuedAssetsOrChanges::Updated(issued_assets) => issued_assets, - IssuedAssetsOrChanges::Change(issued_assets_change) => issued_assets_change + let updated_issued_assets = match issued_assets_or_changes.clone() { + IssuedAssetsOrChange::Updated(issued_assets) => issued_assets, + IssuedAssetsOrChange::Change(issued_assets_change) => issued_assets_change .apply_with(|asset_base| zebra_db.issued_asset(&asset_base).unwrap_or_default()), - IssuedAssetsOrChanges::BurnAndIssuanceChanges { .. } => { - panic!("unexpected variant returned from `combine()`") - } }; for (asset_base, updated_issued_asset_state) in updated_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 403b29999f2..638ecd1ae70 100644 --- a/zebra-state/src/service/non_finalized_state/chain.rs +++ b/zebra-state/src/service/non_finalized_state/chain.rs @@ -972,7 +972,8 @@ impl Chain { } } else { trace!(?position, "reverting changes to issued assets"); - for (asset_base, change) in IssuedAssetsChange::combined_from_transactions(transactions) + for (asset_base, change) in IssuedAssetsChange::from_transactions(transactions) + .expect("blocks in chain state must be valid") { self.issued_assets .entry(asset_base) From 9e0e043175359e98ee5bf8007a6eae97bf41b417 Mon Sep 17 00:00:00 2001 From: Arya Date: Wed, 13 Nov 2024 22:17:01 -0500 Subject: [PATCH 08/35] Fixes tests by computing an `IssuedAssetsChange` for conversions to CheckpointVerifiedBlock --- zebra-rpc/src/sync.rs | 6 +++--- zebra-state/src/arbitrary.rs | 4 +++- zebra-state/src/request.rs | 6 +++++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/zebra-rpc/src/sync.rs b/zebra-rpc/src/sync.rs index fd323ef64bb..787b2e7c5a8 100644 --- a/zebra-rpc/src/sync.rs +++ b/zebra-rpc/src/sync.rs @@ -13,8 +13,8 @@ use zebra_chain::{ }; use zebra_node_services::rpc_client::RpcRequestClient; use zebra_state::{ - spawn_init_read_only, ChainTipBlock, ChainTipChange, ChainTipSender, CheckpointVerifiedBlock, - LatestChainTip, NonFinalizedState, ReadStateService, SemanticallyVerifiedBlock, ZebraDb, + spawn_init_read_only, ChainTipBlock, ChainTipChange, ChainTipSender, LatestChainTip, + NonFinalizedState, ReadStateService, SemanticallyVerifiedBlock, ZebraDb, MAX_BLOCK_REORG_HEIGHT, }; @@ -262,7 +262,7 @@ impl TrustedChainSync { tokio::task::spawn_blocking(move || { let (height, hash) = db.tip()?; db.block(height.into()) - .map(|block| CheckpointVerifiedBlock::with_hash(block, hash)) + .map(|block| SemanticallyVerifiedBlock::with_hash(block, hash)) .map(ChainTipBlock::from) }) .wait_for_panics() diff --git a/zebra-state/src/arbitrary.rs b/zebra-state/src/arbitrary.rs index c5ec8ef3a82..2c1efef3753 100644 --- a/zebra-state/src/arbitrary.rs +++ b/zebra-state/src/arbitrary.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use zebra_chain::{ amount::Amount, block::{self, Block}, + orchard_zsa::IssuedAssetsChange, transaction::Transaction, transparent, value_balance::ValueBalance, @@ -30,6 +31,7 @@ impl Prepare for Arc { let transaction_hashes: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect(); let new_outputs = transparent::new_ordered_outputs_with_height(&block, height, &transaction_hashes); + let issued_assets_change = IssuedAssetsChange::from_transactions(&block.transactions); SemanticallyVerifiedBlock { block, @@ -38,7 +40,7 @@ impl Prepare for Arc { new_outputs, transaction_hashes, deferred_balance: None, - issued_assets_change: None, + issued_assets_change, } } } diff --git a/zebra-state/src/request.rs b/zebra-state/src/request.rs index 3c0e3834278..bdce59adf0f 100644 --- a/zebra-state/src/request.rs +++ b/zebra-state/src/request.rs @@ -528,7 +528,11 @@ impl SemanticallyVerifiedBlock { impl From> for CheckpointVerifiedBlock { fn from(block: Arc) -> Self { - CheckpointVerifiedBlock(SemanticallyVerifiedBlock::from(block)) + let mut block = SemanticallyVerifiedBlock::from(block); + block.issued_assets_change = + IssuedAssetsChange::from_transactions(&block.block.transactions); + + CheckpointVerifiedBlock(block) } } From 8f26a891516a559c655b2c798da303d07c2f0788 Mon Sep 17 00:00:00 2001 From: Arya Date: Wed, 13 Nov 2024 22:58:10 -0500 Subject: [PATCH 09/35] fixes finalization checks --- zebra-chain/src/orchard_zsa/asset_state.rs | 28 +++++++++++++++------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/zebra-chain/src/orchard_zsa/asset_state.rs b/zebra-chain/src/orchard_zsa/asset_state.rs index d355c1d0825..49e5191dcc2 100644 --- a/zebra-chain/src/orchard_zsa/asset_state.rs +++ b/zebra-chain/src/orchard_zsa/asset_state.rs @@ -92,21 +92,20 @@ impl AssetState { /// Updates and returns self with the provided [`AssetStateChange`] if /// the change is valid, or returns None otherwise. pub fn apply_change(self, change: AssetStateChange) -> Option { - self.apply_finalization(change.is_finalized)? - .apply_supply_change(change.supply_change) + self.apply_finalization(change)?.apply_supply_change(change) } - fn apply_finalization(mut self, is_finalized: bool) -> Option { - if self.is_finalized { + fn apply_finalization(mut self, change: AssetStateChange) -> Option { + if self.is_finalized && change.is_issuance() { None } else { - self.is_finalized = is_finalized; + self.is_finalized |= change.is_finalized; Some(self) } } - fn apply_supply_change(mut self, supply_change: SupplyChange) -> Option { - self.total_supply = supply_change.apply_to(self.total_supply)?; + fn apply_supply_change(mut self, change: AssetStateChange) -> Option { + self.total_supply = change.supply_change.apply_to(self.total_supply)?; Some(self) } @@ -114,7 +113,7 @@ impl AssetState { pub fn revert_change(&mut self, change: AssetStateChange) { *self = self .revert_finalization(change.is_finalized) - .apply_supply_change(-change.supply_change) + .revert_supply_change(change) .expect("reverted change should be validated"); } @@ -122,6 +121,11 @@ impl AssetState { self.is_finalized &= !is_finalized; self } + + fn revert_supply_change(mut self, change: AssetStateChange) -> Option { + self.total_supply = (-change.supply_change).apply_to(self.total_supply)?; + Some(self) + } } impl From> for IssuedAssets { @@ -197,6 +201,9 @@ impl AssetStateChange { /// Updates and returns self with the provided [`AssetStateChange`] if /// the change is valid, or returns None otherwise. pub fn apply_change(&mut self, change: AssetStateChange) -> bool { + if self.is_finalized && change.is_issuance() { + return false; + } self.is_finalized |= change.is_finalized; self.supply_change.add(change.supply_change) } @@ -205,6 +212,11 @@ impl AssetStateChange { pub fn is_burn(&self) -> bool { matches!(self.supply_change, SupplyChange::Burn(_)) } + + /// Returns true if the AssetStateChange is for an asset burn. + pub fn is_issuance(&self) -> bool { + matches!(self.supply_change, SupplyChange::Issuance(_)) + } } /// An `issued_asset` map From e063729bcdb55ed368e7fe7c3a654b9206e9da6b Mon Sep 17 00:00:00 2001 From: Arya Date: Fri, 15 Nov 2024 17:52:49 -0500 Subject: [PATCH 10/35] Adds documentation to types and methods in `asset_state` module, fixes several bugs. --- zebra-chain/src/orchard_zsa/asset_state.rs | 122 ++++++++++++------ zebra-consensus/src/block.rs | 36 +++--- zebra-consensus/src/transaction.rs | 6 + zebra-state/src/arbitrary.rs | 7 +- zebra-state/src/request.rs | 40 +++--- zebra-state/src/service/chain_tip.rs | 2 +- zebra-state/src/service/check/issuance.rs | 70 ++++++---- .../zebra_db/block/tests/vectors.rs | 4 +- .../src/service/non_finalized_state/chain.rs | 14 +- 9 files changed, 185 insertions(+), 116 deletions(-) diff --git a/zebra-chain/src/orchard_zsa/asset_state.rs b/zebra-chain/src/orchard_zsa/asset_state.rs index 49e5191dcc2..e8ebdf57109 100644 --- a/zebra-chain/src/orchard_zsa/asset_state.rs +++ b/zebra-chain/src/orchard_zsa/asset_state.rs @@ -27,7 +27,9 @@ pub struct AssetState { #[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct AssetStateChange { /// Whether the asset should be finalized such that no more of it can be issued. - pub is_finalized: bool, + pub should_finalize: bool, + /// Whether the asset should be finalized such that no more of it can be issued. + pub includes_issuance: bool, /// The change in supply from newly issued assets or burned assets, if any. pub supply_change: SupplyChange, } @@ -35,7 +37,10 @@ pub struct AssetStateChange { #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] /// An asset supply change to apply to the issued assets map. pub enum SupplyChange { + /// An issuance that should increase the total supply of an asset Issuance(u64), + + /// A burn that should reduce the total supply of an asset. Burn(u64), } @@ -46,6 +51,9 @@ impl Default for SupplyChange { } impl SupplyChange { + /// Applies `self` to a provided `total_supply` of an asset. + /// + /// Returns the updated total supply after the [`SupplyChange`] has been applied. fn apply_to(self, total_supply: u64) -> Option { match self { SupplyChange::Issuance(amount) => total_supply.checked_add(amount), @@ -53,6 +61,8 @@ impl SupplyChange { } } + /// Returns the [`SupplyChange`] amount as an [`i128`] where burned amounts + /// are negative. fn as_i128(self) -> i128 { match self { SupplyChange::Issuance(amount) => i128::from(amount), @@ -60,11 +70,16 @@ impl SupplyChange { } } + /// Attempts to add another supply change to `self`. + /// + /// Returns true if successful or false if the result would be invalid. fn add(&mut self, rhs: Self) -> bool { if let Some(result) = self .as_i128() .checked_add(rhs.as_i128()) .and_then(|signed| match signed { + // Burn amounts MUST not be 0 + // TODO: Reference ZIP 0.. => signed.try_into().ok().map(Self::Issuance), ..0 => signed.try_into().ok().map(Self::Burn), }) @@ -75,6 +90,11 @@ impl SupplyChange { false } } + + /// Returns true if this [`SupplyChange`] is an issuance. + pub fn is_issuance(&self) -> bool { + matches!(self, SupplyChange::Issuance(_)) + } } impl std::ops::Neg for SupplyChange { @@ -95,15 +115,19 @@ impl AssetState { self.apply_finalization(change)?.apply_supply_change(change) } + /// Updates the `is_finalized` field on `self` if the change is valid and + /// returns `self`, or returns None otherwise. fn apply_finalization(mut self, change: AssetStateChange) -> Option { - if self.is_finalized && change.is_issuance() { + if self.is_finalized && change.includes_issuance { None } else { - self.is_finalized |= change.is_finalized; + self.is_finalized |= change.should_finalize; Some(self) } } + /// Updates the `supply_change` field on `self` if the change is valid and + /// returns `self`, or returns None otherwise. fn apply_supply_change(mut self, change: AssetStateChange) -> Option { self.total_supply = change.supply_change.apply_to(self.total_supply)?; Some(self) @@ -112,16 +136,18 @@ impl AssetState { /// Reverts the provided [`AssetStateChange`]. pub fn revert_change(&mut self, change: AssetStateChange) { *self = self - .revert_finalization(change.is_finalized) + .revert_finalization(change.should_finalize) .revert_supply_change(change) .expect("reverted change should be validated"); } - fn revert_finalization(mut self, is_finalized: bool) -> Self { - self.is_finalized &= !is_finalized; + /// Reverts the changes to `is_finalized` from the provied [`AssetStateChange`]. + fn revert_finalization(mut self, should_finalize: bool) -> Self { + self.is_finalized &= !should_finalize; self } + /// Reverts the changes to `supply_change` from the provied [`AssetStateChange`]. fn revert_supply_change(mut self, change: AssetStateChange) -> Option { self.total_supply = (-change.supply_change).apply_to(self.total_supply)?; Some(self) @@ -135,31 +161,40 @@ impl From> for IssuedAssets { } impl AssetStateChange { + /// Creates a new [`AssetStateChange`] from an asset base, supply change, and + /// `should_finalize` flag. fn new( asset_base: AssetBase, supply_change: SupplyChange, - is_finalized: bool, + should_finalize: bool, ) -> (AssetBase, Self) { ( asset_base, Self { - is_finalized, + should_finalize, + includes_issuance: supply_change.is_issuance(), supply_change, }, ) } + /// Accepts a transaction and returns an iterator of asset bases and issued asset state changes + /// that should be applied to those asset bases when committing the transaction to the chain state. fn from_transaction(tx: &Arc) -> impl Iterator + '_ { Self::from_burns(tx.orchard_burns()) .chain(Self::from_issue_actions(tx.orchard_issue_actions())) } + /// Accepts an iterator of [`IssueAction`]s and returns an iterator of asset bases and issued asset state changes + /// that should be applied to those asset bases when committing the provided issue actions to the chain state. fn from_issue_actions<'a>( actions: impl Iterator + 'a, ) -> impl Iterator + 'a { actions.flat_map(Self::from_issue_action) } + /// Accepts an [`IssueAction`] and returns an iterator of asset bases and issued asset state changes + /// that should be applied to those asset bases when committing the provided issue action to the chain state. fn from_issue_action(action: &IssueAction) -> impl Iterator + '_ { let supply_changes = Self::from_notes(action.notes()); let finalize_changes = action @@ -178,10 +213,14 @@ impl AssetStateChange { supply_changes.chain(finalize_changes) } + /// Accepts an iterator of [`orchard::Note`]s and returns an iterator of asset bases and issued asset state changes + /// that should be applied to those asset bases when committing the provided orchard notes to the chain state. fn from_notes(notes: &[orchard::Note]) -> impl Iterator + '_ { notes.iter().copied().map(Self::from_note) } + /// Accepts an [`orchard::Note`] and returns an iterator of asset bases and issued asset state changes + /// that should be applied to those asset bases when committing the provided orchard note to the chain state. fn from_note(note: orchard::Note) -> (AssetBase, Self) { Self::new( note.asset(), @@ -190,10 +229,14 @@ impl AssetStateChange { ) } + /// Accepts an iterator of [`BurnItem`]s and returns an iterator of asset bases and issued asset state changes + /// that should be applied to those asset bases when committing the provided asset burns to the chain state. fn from_burns(burns: &[BurnItem]) -> impl Iterator + '_ { burns.iter().map(Self::from_burn) } + /// Accepts an [`BurnItem`] and returns an iterator of asset bases and issued asset state changes + /// that should be applied to those asset bases when committing the provided burn to the chain state. fn from_burn(burn: &BurnItem) -> (AssetBase, Self) { Self::new(burn.asset(), SupplyChange::Burn(burn.amount()), false) } @@ -201,25 +244,16 @@ impl AssetStateChange { /// Updates and returns self with the provided [`AssetStateChange`] if /// the change is valid, or returns None otherwise. pub fn apply_change(&mut self, change: AssetStateChange) -> bool { - if self.is_finalized && change.is_issuance() { + if self.should_finalize && change.includes_issuance { return false; } - self.is_finalized |= change.is_finalized; + self.should_finalize |= change.should_finalize; + self.includes_issuance |= change.includes_issuance; self.supply_change.add(change.supply_change) } - - /// Returns true if the AssetStateChange is for an asset burn. - pub fn is_burn(&self) -> bool { - matches!(self.supply_change, SupplyChange::Burn(_)) - } - - /// Returns true if the AssetStateChange is for an asset burn. - pub fn is_issuance(&self) -> bool { - matches!(self.supply_change, SupplyChange::Issuance(_)) - } } -/// An `issued_asset` map +/// An map of issued asset states by asset base. // TODO: Reference ZIP #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct IssuedAssets(HashMap); @@ -235,10 +269,9 @@ impl IssuedAssets { self.0.iter() } - fn update<'a>(&mut self, issued_assets: impl Iterator + 'a) { - for (asset_base, asset_state) in issued_assets { - self.0.insert(asset_base, asset_state); - } + /// Extends inner [`HashMap`] with updated asset states from the provided iterator + fn extend<'a>(&mut self, issued_assets: impl Iterator + 'a) { + self.0.extend(issued_assets); } } @@ -257,10 +290,12 @@ impl IntoIterator for IssuedAssets { pub struct IssuedAssetsChange(HashMap); impl IssuedAssetsChange { + /// Creates a new [`IssuedAssetsChange`]. fn new() -> Self { Self(HashMap::new()) } + /// Applies changes in the provided iterator to an [`IssuedAssetsChange`]. fn update<'a>( &mut self, changes: impl Iterator + 'a, @@ -274,22 +309,28 @@ impl IssuedAssetsChange { true } - /// Accepts a slice of [`Arc`]s. + /// Accepts a [`Arc`]. /// /// Returns an [`IssuedAssetsChange`] representing all of the changes to the issued assets - /// map that should be applied for the provided transactions. - pub fn from_transactions(transactions: &[Arc]) -> Option { + /// map that should be applied for the provided transaction, or `None` if the change would be invalid. + pub fn from_transaction(transaction: &Arc) -> Option { let mut issued_assets_change = Self::new(); - for transaction in transactions { - if !issued_assets_change.update(AssetStateChange::from_transaction(transaction)) { - return None; - } + if !issued_assets_change.update(AssetStateChange::from_transaction(transaction)) { + return None; } Some(issued_assets_change) } + /// Accepts a slice of [`Arc`]s. + /// + /// Returns an [`IssuedAssetsChange`] representing all of the changes to the issued assets + /// map that should be applied for the provided transactions. + pub fn from_transactions(transactions: &[Arc]) -> Option> { + transactions.iter().map(Self::from_transaction).collect() + } + /// Consumes self and accepts a closure for looking up previous asset states. /// /// Applies changes in self to the previous asset state. @@ -298,7 +339,7 @@ impl IssuedAssetsChange { pub fn apply_with(self, f: impl Fn(AssetBase) -> AssetState) -> IssuedAssets { let mut issued_assets = IssuedAssets::new(); - issued_assets.update(self.0.into_iter().map(|(asset_base, change)| { + issued_assets.extend(self.0.into_iter().map(|(asset_base, change)| { ( asset_base, f(asset_base) @@ -309,6 +350,11 @@ impl IssuedAssetsChange { issued_assets } + + /// Iterates over the inner [`HashMap`] of asset bases and state changes. + pub fn iter(&self) -> impl Iterator + '_ { + self.0.iter().map(|(&base, &state)| (base, state)) + } } impl std::ops::Add for IssuedAssetsChange { @@ -324,13 +370,3 @@ impl std::ops::Add for IssuedAssetsChange { } } } - -impl IntoIterator for IssuedAssetsChange { - type Item = (AssetBase, AssetStateChange); - - type IntoIter = std::collections::hash_map::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} diff --git a/zebra-consensus/src/block.rs b/zebra-consensus/src/block.rs index 6728bd9be66..aa4bf94c72f 100644 --- a/zebra-consensus/src/block.rs +++ b/zebra-consensus/src/block.rs @@ -15,7 +15,7 @@ use std::{ }; use chrono::Utc; -use futures::stream::FuturesUnordered; +use futures::stream::FuturesOrdered; use futures_util::FutureExt; use thiserror::Error; use tower::{Service, ServiceExt}; @@ -24,7 +24,6 @@ use tracing::Instrument; use zebra_chain::{ amount::Amount, block, - orchard_zsa::IssuedAssetsChange, parameters::{subsidy::FundingStreamReceiver, Network}, transparent, work::equihash, @@ -227,7 +226,7 @@ where tx::check::coinbase_outputs_are_decryptable(&coinbase_tx, &network, height)?; // Send transactions to the transaction verifier to be checked - let mut async_checks = FuturesUnordered::new(); + let mut async_checks = FuturesOrdered::new(); let known_utxos = Arc::new(transparent::new_ordered_outputs( &block, @@ -244,7 +243,7 @@ where height, time: block.header.time, }); - async_checks.push(rsp); + async_checks.push_back(rsp); } tracing::trace!(len = async_checks.len(), "built async tx checks"); @@ -253,26 +252,32 @@ where // Sum up some block totals from the transaction responses. let mut legacy_sigop_count = 0; let mut block_miner_fees = Ok(Amount::zero()); + let mut issued_assets_changes = Vec::new(); use futures::StreamExt; while let Some(result) = async_checks.next().await { tracing::trace!(?result, remaining = async_checks.len()); - let response = result + let crate::transaction::Response::Block { + tx_id: _, + miner_fee, + legacy_sigop_count: tx_legacy_sigop_count, + issued_assets_change, + } = result .map_err(Into::into) - .map_err(VerifyBlockError::Transaction)?; - - assert!( - matches!(response, tx::Response::Block { .. }), - "unexpected response from transaction verifier: {response:?}" - ); + .map_err(VerifyBlockError::Transaction)? + else { + panic!("unexpected response from transaction verifier"); + }; - legacy_sigop_count += response.legacy_sigop_count(); + legacy_sigop_count += tx_legacy_sigop_count; // Coinbase transactions consume the miner fee, // so they don't add any value to the block's total miner fee. - if let Some(miner_fee) = response.miner_fee() { + if let Some(miner_fee) = miner_fee { block_miner_fees += miner_fee; } + + issued_assets_changes.push(issued_assets_change); } // Check the summed block totals @@ -315,9 +320,6 @@ where let new_outputs = Arc::into_inner(known_utxos) .expect("all verification tasks using known_utxos are complete"); - let issued_assets_change = IssuedAssetsChange::from_transactions(&block.transactions) - .ok_or(TransactionError::InvalidAssetIssuanceOrBurn)?; - let prepared_block = zs::SemanticallyVerifiedBlock { block, hash, @@ -325,7 +327,7 @@ where new_outputs, transaction_hashes, deferred_balance: Some(expected_deferred_amount), - issued_assets_change: Some(issued_assets_change), + issued_assets_changes: issued_assets_changes.into(), }; // Return early for proposal requests when getblocktemplate-rpcs feature is enabled diff --git a/zebra-consensus/src/transaction.rs b/zebra-consensus/src/transaction.rs index e91f576f2a5..11af08c3907 100644 --- a/zebra-consensus/src/transaction.rs +++ b/zebra-consensus/src/transaction.rs @@ -19,6 +19,7 @@ use tracing::Instrument; use zebra_chain::{ amount::{Amount, NonNegative}, block, orchard, + orchard_zsa::IssuedAssetsChange, parameters::{Network, NetworkUpgrade}, primitives::Groth16Proof, sapling, @@ -143,6 +144,10 @@ pub enum Response { /// The number of legacy signature operations in this transaction's /// transparent inputs and outputs. legacy_sigop_count: u64, + + /// The changes to the issued assets map that should be applied for + /// this transaction. + issued_assets_change: IssuedAssetsChange, }, /// A response to a mempool transaction verification request. @@ -473,6 +478,7 @@ where tx_id, miner_fee, legacy_sigop_count, + issued_assets_change: IssuedAssetsChange::from_transaction(&tx).ok_or(TransactionError::InvalidAssetIssuanceOrBurn)?, }, Request::Mempool { transaction, .. } => { let transaction = VerifiedUnminedTx::new( diff --git a/zebra-state/src/arbitrary.rs b/zebra-state/src/arbitrary.rs index 2c1efef3753..183567b5794 100644 --- a/zebra-state/src/arbitrary.rs +++ b/zebra-state/src/arbitrary.rs @@ -31,7 +31,8 @@ impl Prepare for Arc { let transaction_hashes: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect(); let new_outputs = transparent::new_ordered_outputs_with_height(&block, height, &transaction_hashes); - let issued_assets_change = IssuedAssetsChange::from_transactions(&block.transactions); + let issued_assets_changes = IssuedAssetsChange::from_transactions(&block.transactions) + .expect("prepared blocks should be semantically valid"); SemanticallyVerifiedBlock { block, @@ -40,7 +41,7 @@ impl Prepare for Arc { new_outputs, transaction_hashes, deferred_balance: None, - issued_assets_change, + issued_assets_changes, } } } @@ -119,7 +120,7 @@ impl ContextuallyVerifiedBlock { new_outputs, transaction_hashes, deferred_balance: _, - issued_assets_change: _, + issued_assets_changes: _, } = block.into(); Self { diff --git a/zebra-state/src/request.rs b/zebra-state/src/request.rs index bdce59adf0f..0cfa791001c 100644 --- a/zebra-state/src/request.rs +++ b/zebra-state/src/request.rs @@ -164,9 +164,9 @@ pub struct SemanticallyVerifiedBlock { pub transaction_hashes: Arc<[transaction::Hash]>, /// This block's contribution to the deferred pool. pub deferred_balance: Option>, - /// A map of burns to be applied to the issued assets map. - // TODO: Reference ZIP. - pub issued_assets_change: Option, + /// A precomputed list of the [`IssuedAssetsChange`]s for the transactions in this block, + /// in the same order as `block.transactions`. + pub issued_assets_changes: Arc<[IssuedAssetsChange]>, } /// A block ready to be committed directly to the finalized state with @@ -319,9 +319,15 @@ pub enum IssuedAssetsOrChange { Change(IssuedAssetsChange), } -impl From for IssuedAssetsOrChange { - fn from(change: IssuedAssetsChange) -> Self { - Self::Change(change) +impl From> for IssuedAssetsOrChange { + fn from(change: Arc<[IssuedAssetsChange]>) -> Self { + Self::Change( + change + .iter() + .cloned() + .reduce(|a, b| a + b) + .unwrap_or_default(), + ) } } @@ -334,11 +340,7 @@ impl From for IssuedAssetsOrChange { impl FinalizedBlock { /// Constructs [`FinalizedBlock`] from [`CheckpointVerifiedBlock`] and its [`Treestate`]. pub fn from_checkpoint_verified(block: CheckpointVerifiedBlock, treestate: Treestate) -> Self { - let issued_assets = block - .issued_assets_change - .clone() - .expect("checkpoint verified block should have issued assets change") - .into(); + let issued_assets = block.issued_assets_changes.clone().into(); Self::from_semantically_verified( SemanticallyVerifiedBlock::from(block), @@ -449,7 +451,7 @@ impl ContextuallyVerifiedBlock { new_outputs, transaction_hashes, deferred_balance, - issued_assets_change: _, + issued_assets_changes: _, } = semantically_verified; // This is redundant for the non-finalized state, @@ -485,7 +487,7 @@ impl CheckpointVerifiedBlock { let issued_assets_change = IssuedAssetsChange::from_transactions(&block.transactions)?; let mut block = Self::with_hash(block.clone(), hash.unwrap_or(block.hash())); block.deferred_balance = deferred_balance; - block.issued_assets_change = Some(issued_assets_change); + block.issued_assets_changes = issued_assets_change; Some(block) } @@ -515,7 +517,7 @@ impl SemanticallyVerifiedBlock { new_outputs, transaction_hashes, deferred_balance: None, - issued_assets_change: None, + issued_assets_changes: Arc::new([]), } } @@ -528,11 +530,7 @@ impl SemanticallyVerifiedBlock { impl From> for CheckpointVerifiedBlock { fn from(block: Arc) -> Self { - let mut block = SemanticallyVerifiedBlock::from(block); - block.issued_assets_change = - IssuedAssetsChange::from_transactions(&block.block.transactions); - - CheckpointVerifiedBlock(block) + Self(SemanticallyVerifiedBlock::from(block)) } } @@ -552,7 +550,7 @@ impl From> for SemanticallyVerifiedBlock { new_outputs, transaction_hashes, deferred_balance: None, - issued_assets_change: None, + issued_assets_changes: Arc::new([]), } } } @@ -572,7 +570,7 @@ impl From for SemanticallyVerifiedBlock { .constrain::() .expect("deferred balance in a block must me non-negative"), ), - issued_assets_change: None, + issued_assets_changes: Arc::new([]), } } } diff --git a/zebra-state/src/service/chain_tip.rs b/zebra-state/src/service/chain_tip.rs index 95306b54282..8a0ed517766 100644 --- a/zebra-state/src/service/chain_tip.rs +++ b/zebra-state/src/service/chain_tip.rs @@ -116,7 +116,7 @@ impl From for ChainTipBlock { new_outputs: _, transaction_hashes, deferred_balance: _, - issued_assets_change: _, + issued_assets_changes: _, } = prepared; Self { diff --git a/zebra-state/src/service/check/issuance.rs b/zebra-state/src/service/check/issuance.rs index daf7635e68a..abff882c33c 100644 --- a/zebra-state/src/service/check/issuance.rs +++ b/zebra-state/src/service/check/issuance.rs @@ -2,45 +2,67 @@ use std::{collections::HashMap, sync::Arc}; -use zebra_chain::orchard_zsa::IssuedAssets; +use zebra_chain::orchard_zsa::{AssetBase, AssetState, IssuedAssets}; use crate::{SemanticallyVerifiedBlock, ValidateContextError, ZebraDb}; use super::Chain; +// TODO: Factor out chain/disk read to a fn in the `read` module. +fn asset_state( + finalized_state: &ZebraDb, + parent_chain: &Arc, + issued_assets: &HashMap, + asset_base: &AssetBase, +) -> Option { + issued_assets + .get(asset_base) + .copied() + .or_else(|| parent_chain.issued_asset(asset_base)) + .or_else(|| finalized_state.issued_asset(asset_base)) +} + pub fn valid_burns_and_issuance( finalized_state: &ZebraDb, parent_chain: &Arc, semantically_verified: &SemanticallyVerifiedBlock, ) -> Result { - let Some(issued_assets_change) = semantically_verified.issued_assets_change.clone() else { - return Ok(IssuedAssets::default()); - }; - let mut issued_assets = HashMap::new(); - for (asset_base, change) in issued_assets_change { - let asset_state = issued_assets - .get(&asset_base) - .copied() - .or_else(|| parent_chain.issued_asset(&asset_base)) - .or_else(|| finalized_state.issued_asset(&asset_base)); + for (issued_assets_change, transaction) in semantically_verified + .issued_assets_changes + .iter() + .zip(&semantically_verified.block.transactions) + { + // Check that no burn item attempts to burn more than the issued supply for an asset + for burn in transaction.orchard_burns() { + let asset_base = burn.asset(); + let asset_state = + asset_state(finalized_state, parent_chain, &issued_assets, &asset_base) + .ok_or(ValidateContextError::InvalidBurn)?; - let updated_asset_state = if change.is_burn() { - asset_state - .ok_or(ValidateContextError::InvalidBurn)? - .apply_change(change) - .ok_or(ValidateContextError::InvalidBurn)? - } else { - asset_state - .unwrap_or_default() + if asset_state.total_supply < burn.amount() { + return Err(ValidateContextError::InvalidBurn); + } else { + // Any burned asset bases in the transaction will also be present in the issued assets change, + // adding a copy of initial asset state to `issued_assets` avoids duplicate disk reads. + issued_assets.insert(asset_base, asset_state); + } + } + + for (asset_base, change) in issued_assets_change.iter() { + let asset_state = + asset_state(finalized_state, parent_chain, &issued_assets, &asset_base) + .unwrap_or_default(); + + let updated_asset_state = asset_state .apply_change(change) - .ok_or(ValidateContextError::InvalidIssuance)? - }; + .ok_or(ValidateContextError::InvalidIssuance)?; - issued_assets - .insert(asset_base, updated_asset_state) - .expect("transactions must have only one burn item per asset base"); + issued_assets + .insert(asset_base, updated_asset_state) + .expect("transactions must have only one burn item per asset base"); + } } Ok(issued_assets.into()) diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs b/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs index 7b35af87a75..d7df21fda0e 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs @@ -130,7 +130,7 @@ fn test_block_db_round_trip_with( .collect(); let new_outputs = new_ordered_outputs_with_height(&original_block, Height(0), &transaction_hashes); - let issued_assets_change = + let issued_assets_changes = IssuedAssetsChange::from_transactions(&original_block.transactions) .expect("issued assets should be valid"); @@ -141,7 +141,7 @@ fn test_block_db_round_trip_with( new_outputs, transaction_hashes, deferred_balance: None, - issued_assets_change: Some(issued_assets_change), + issued_assets_changes, }) }; diff --git a/zebra-state/src/service/non_finalized_state/chain.rs b/zebra-state/src/service/non_finalized_state/chain.rs index 638ecd1ae70..30f838afbab 100644 --- a/zebra-state/src/service/non_finalized_state/chain.rs +++ b/zebra-state/src/service/non_finalized_state/chain.rs @@ -972,13 +972,17 @@ impl Chain { } } else { trace!(?position, "reverting changes to issued assets"); - for (asset_base, change) in IssuedAssetsChange::from_transactions(transactions) + for issued_assets_change in IssuedAssetsChange::from_transactions(transactions) .expect("blocks in chain state must be valid") + .iter() + .rev() { - self.issued_assets - .entry(asset_base) - .or_default() - .revert_change(change); + for (asset_base, change) in issued_assets_change.iter() { + self.issued_assets + .entry(asset_base) + .or_default() + .revert_change(change); + } } } } From f0b64ad8d79e20d044bf16beed828dee9523f0d9 Mon Sep 17 00:00:00 2001 From: Dmitry Demin Date: Wed, 27 Nov 2024 10:02:45 +0100 Subject: [PATCH 11/35] Fix compilation errors that appeared after the previous merge --- zebra-chain/src/orchard_zsa/burn.rs | 10 +++++++++- zebra-chain/src/orchard_zsa/issuance.rs | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/zebra-chain/src/orchard_zsa/burn.rs b/zebra-chain/src/orchard_zsa/burn.rs index 18ee3f9f1f0..23bd133257f 100644 --- a/zebra-chain/src/orchard_zsa/burn.rs +++ b/zebra-chain/src/orchard_zsa/burn.rs @@ -5,6 +5,7 @@ use std::io; use halo2::pasta::pallas; use crate::{ + amount::Amount, block::MAX_BLOCK_BYTES, orchard::ValueCommitment, serialization::{ @@ -166,7 +167,14 @@ impl From for ValueCommitment { burn.0 .into_iter() .map(|BurnItem(asset, amount)| { - ValueCommitment::with_asset(pallas::Scalar::zero(), amount, &asset) + ValueCommitment::with_asset( + pallas::Scalar::zero(), + // FIXME: consider to use TryFrom and return an error instead of using "expect" + amount + .try_into() + .expect("should convert Burn amount to i64"), + &asset, + ) }) .sum() } diff --git a/zebra-chain/src/orchard_zsa/issuance.rs b/zebra-chain/src/orchard_zsa/issuance.rs index 4b4de1e0fce..32b8cbf22ae 100644 --- a/zebra-chain/src/orchard_zsa/issuance.rs +++ b/zebra-chain/src/orchard_zsa/issuance.rs @@ -7,6 +7,8 @@ use halo2::pasta::pallas; // For pallas::Base::from_repr only use group::ff::PrimeField; +use nonempty::NonEmpty; + use zcash_primitives::transaction::components::issuance::{read_v6_bundle, write_v6_bundle}; use orchard::{ From bc0c8e63dcd8108792f4ad8ddcbba26ea1429adf Mon Sep 17 00:00:00 2001 From: Dmitry Demin Date: Wed, 27 Nov 2024 10:06:08 +0100 Subject: [PATCH 12/35] Avoid using NonEmpty in orchard_zsa/issuance --- zebra-chain/src/orchard_zsa/issuance.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/zebra-chain/src/orchard_zsa/issuance.rs b/zebra-chain/src/orchard_zsa/issuance.rs index 32b8cbf22ae..36b31f65de7 100644 --- a/zebra-chain/src/orchard_zsa/issuance.rs +++ b/zebra-chain/src/orchard_zsa/issuance.rs @@ -7,8 +7,6 @@ use halo2::pasta::pallas; // For pallas::Base::from_repr only use group::ff::PrimeField; -use nonempty::NonEmpty; - use zcash_primitives::transaction::components::issuance::{read_v6_bundle, write_v6_bundle}; use orchard::{ @@ -57,8 +55,8 @@ impl IssueData { } /// Returns issuance actions - pub fn actions(&self) -> &NonEmpty { - self.0.actions() + pub fn actions(&self) -> impl Iterator { + self.0.actions().iter() } } From 17f3ee6f8653d11d8812f393450db975ef62e91e Mon Sep 17 00:00:00 2001 From: Dmitry Demin Date: Wed, 27 Nov 2024 10:18:13 +0100 Subject: [PATCH 13/35] Fix BurnItem serialization/deserializartioon errors (use LE instead of BE for amount, read amount after asset base) --- zebra-chain/src/orchard_zsa/burn.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/zebra-chain/src/orchard_zsa/burn.rs b/zebra-chain/src/orchard_zsa/burn.rs index 23bd133257f..fce70413d50 100644 --- a/zebra-chain/src/orchard_zsa/burn.rs +++ b/zebra-chain/src/orchard_zsa/burn.rs @@ -37,6 +37,7 @@ const AMOUNT_SIZE: u64 = 8; // FIXME: is this a correct way to calculate (simple sum of sizes of components)? const BURN_ITEM_SIZE: u64 = ASSET_BASE_SIZE + AMOUNT_SIZE; +// FIXME: Define BurnItem (or, even Burn/NoBurn) in Orchard and reuse it here? /// Orchard ZSA burn item. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct BurnItem(AssetBase, u64); @@ -67,7 +68,7 @@ impl ZcashSerialize for BurnItem { let BurnItem(asset_base, amount) = self; asset_base.zcash_serialize(&mut writer)?; - writer.write_all(&amount.to_be_bytes())?; + writer.write_all(&amount.to_le_bytes())?; Ok(()) } @@ -75,12 +76,10 @@ impl ZcashSerialize for BurnItem { impl ZcashDeserialize for BurnItem { fn zcash_deserialize(mut reader: R) -> Result { + let asset_base = AssetBase::zcash_deserialize(&mut reader)?; let mut amount_bytes = [0; 8]; reader.read_exact(&mut amount_bytes)?; - Ok(Self( - AssetBase::zcash_deserialize(&mut reader)?, - u64::from_be_bytes(amount_bytes), - )) + Ok(Self(asset_base, u64::from_le_bytes(amount_bytes))) } } From 3f96af0c3bfb1e946c5188b7148f208feef206ce Mon Sep 17 00:00:00 2001 From: Dmitry Demin Date: Wed, 27 Nov 2024 10:19:44 +0100 Subject: [PATCH 14/35] Make a minor fix and add FIXME comment in orchard_flavor_ext.rs --- zebra-chain/src/orchard/orchard_flavor_ext.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zebra-chain/src/orchard/orchard_flavor_ext.rs b/zebra-chain/src/orchard/orchard_flavor_ext.rs index 3bc863f8539..faefaf6a8ba 100644 --- a/zebra-chain/src/orchard/orchard_flavor_ext.rs +++ b/zebra-chain/src/orchard/orchard_flavor_ext.rs @@ -11,12 +11,11 @@ use orchard::{note_encryption::OrchardDomainCommon, orchard_flavor}; use crate::{ orchard::ValueCommitment, - orchard_zsa, serialization::{ZcashDeserialize, ZcashSerialize}, }; #[cfg(feature = "tx-v6")] -use crate::orchard_zsa::{Burn, NoBurn}; +use crate::orchard_zsa::{Burn, BurnItem, NoBurn}; use super::note; @@ -59,8 +58,9 @@ pub trait OrchardFlavorExt: Clone + Debug { + Default + ZcashDeserialize + ZcashSerialize + // FIXME: consider using AsRef instead of Into, to avoid a redundancy + Into - + AsRef<[orchard_zsa::BurnItem]> + + AsRef<[BurnItem]> + TestArbitrary; } From 5524480ae34a125f0671522c7a57a9d7b2fef49c Mon Sep 17 00:00:00 2001 From: Dmitry Demin Date: Wed, 27 Nov 2024 10:22:52 +0100 Subject: [PATCH 15/35] Fix the sign of burn value in SupplyChange::add in orchard_zsa/asset_state, add a couple of FIXMEs --- zebra-chain/src/orchard_zsa/asset_state.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/zebra-chain/src/orchard_zsa/asset_state.rs b/zebra-chain/src/orchard_zsa/asset_state.rs index e8ebdf57109..ec5f1692b8b 100644 --- a/zebra-chain/src/orchard_zsa/asset_state.rs +++ b/zebra-chain/src/orchard_zsa/asset_state.rs @@ -28,6 +28,7 @@ pub struct AssetState { pub struct AssetStateChange { /// Whether the asset should be finalized such that no more of it can be issued. pub should_finalize: bool, + // FIXME: is this a correct comment? /// Whether the asset should be finalized such that no more of it can be issued. pub includes_issuance: bool, /// The change in supply from newly issued assets or burned assets, if any. @@ -50,6 +51,7 @@ impl Default for SupplyChange { } } +// FIXME: can we reuse some functions from orchard crate?s impl SupplyChange { /// Applies `self` to a provided `total_supply` of an asset. /// @@ -81,7 +83,8 @@ impl SupplyChange { // Burn amounts MUST not be 0 // TODO: Reference ZIP 0.. => signed.try_into().ok().map(Self::Issuance), - ..0 => signed.try_into().ok().map(Self::Burn), + // FIXME: (-signed) - is this a correct fix? + ..0 => (-signed).try_into().ok().map(Self::Burn), }) { *self = result; From 8096da44c04324409a2b1bf60307d417872c70f6 Mon Sep 17 00:00:00 2001 From: Dmitry Demin Date: Wed, 27 Nov 2024 10:58:20 +0100 Subject: [PATCH 16/35] Fix the 'transactions must have only one burn item per asset base' error in the function of the crate (this may not be a fully correct fix). Add a couple of FIXME comments explaining the problem. --- zebra-chain/src/orchard_zsa/asset_state.rs | 1 + zebra-state/src/service/check/issuance.rs | 23 +++++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/zebra-chain/src/orchard_zsa/asset_state.rs b/zebra-chain/src/orchard_zsa/asset_state.rs index ec5f1692b8b..7b6746be77d 100644 --- a/zebra-chain/src/orchard_zsa/asset_state.rs +++ b/zebra-chain/src/orchard_zsa/asset_state.rs @@ -211,6 +211,7 @@ impl AssetStateChange { }) .unwrap_or_default() .into_iter() + // FIXME: We use 0 as a value - is that correct? .map(|asset_base| Self::new(asset_base, SupplyChange::Issuance(0), true)); supply_changes.chain(finalize_changes) diff --git a/zebra-state/src/service/check/issuance.rs b/zebra-state/src/service/check/issuance.rs index abff882c33c..854f972a13b 100644 --- a/zebra-state/src/service/check/issuance.rs +++ b/zebra-state/src/service/check/issuance.rs @@ -29,6 +29,8 @@ pub fn valid_burns_and_issuance( ) -> Result { let mut issued_assets = HashMap::new(); + // FIXME: Do all checks (for duplication, existence etc.) need to be performed per tranaction, not per + // the entire block? for (issued_assets_change, transaction) in semantically_verified .issued_assets_changes .iter() @@ -41,6 +43,10 @@ pub fn valid_burns_and_issuance( asset_state(finalized_state, parent_chain, &issued_assets, &asset_base) .ok_or(ValidateContextError::InvalidBurn)?; + // FIXME: The check that we can't burn an asset before we issued it is implicit - + // through the check it total_supply < burn.amount (the total supply is zero if the + // asset is not issued). May be validation functions from the orcharfd crate need to be + // reused in a some way? if asset_state.total_supply < burn.amount() { return Err(ValidateContextError::InvalidBurn); } else { @@ -50,6 +56,13 @@ pub fn valid_burns_and_issuance( } } + // FIXME: Not sure: it looks like semantically_verified.issued_assets_changes is already + // filled with burn and issuance items in zebra-consensus, see Verifier::call function in + // zebra-consensus/src/transaction.rs (it uses from_burn and from_issue_action AssetStateChange + // methods from ebra-chain/src/orchard_zsa/asset_state.rs). Can't it cause a duplication? + // Can we collect all change items here, not in zebra-consensus (and so we don't need + // SemanticallyVerifiedBlock::issued_assets_changes at all), or performing part of the + // checks in zebra-consensus is important for the consensus checks order in a some way? for (asset_base, change) in issued_assets_change.iter() { let asset_state = asset_state(finalized_state, parent_chain, &issued_assets, &asset_base) @@ -59,9 +72,13 @@ pub fn valid_burns_and_issuance( .apply_change(change) .ok_or(ValidateContextError::InvalidIssuance)?; - issued_assets - .insert(asset_base, updated_asset_state) - .expect("transactions must have only one burn item per asset base"); + // FIXME: Is it correct to do nothing if the issued_assets aready has asset_base? Now it'd be + // replaced with updated_asset_state in this case (where the duplicated value is added to + // the supply). Block may have two burn records for the same asset but in different + // transactions - it's allowed, that's why the check has been removed. On the other + // hand, there needs to be a check that denies duplicated burn records for the same + // asset inside the same transaction. + issued_assets.insert(asset_base, updated_asset_state); } } From 20fd58d092d00350e3a95517fc5326d354817f90 Mon Sep 17 00:00:00 2001 From: Dmitry Demin Date: Wed, 27 Nov 2024 11:53:14 +0100 Subject: [PATCH 17/35] Use NoteValue from the orchard crate for BurnItem amount instead of u64 to prevent serialization errors and enable defining BurnItem in orchard, facilitating its reuse along with related functions --- zebra-chain/src/orchard_zsa/asset_state.rs | 2 +- zebra-chain/src/orchard_zsa/burn.rs | 25 +++++++++++----------- zebra-state/src/service/check/issuance.rs | 2 +- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/zebra-chain/src/orchard_zsa/asset_state.rs b/zebra-chain/src/orchard_zsa/asset_state.rs index 7b6746be77d..bdb6cd0e21b 100644 --- a/zebra-chain/src/orchard_zsa/asset_state.rs +++ b/zebra-chain/src/orchard_zsa/asset_state.rs @@ -242,7 +242,7 @@ impl AssetStateChange { /// Accepts an [`BurnItem`] and returns an iterator of asset bases and issued asset state changes /// that should be applied to those asset bases when committing the provided burn to the chain state. fn from_burn(burn: &BurnItem) -> (AssetBase, Self) { - Self::new(burn.asset(), SupplyChange::Burn(burn.amount()), false) + Self::new(burn.asset(), SupplyChange::Burn(burn.raw_amount()), false) } /// Updates and returns self with the provided [`AssetStateChange`] if diff --git a/zebra-chain/src/orchard_zsa/burn.rs b/zebra-chain/src/orchard_zsa/burn.rs index fce70413d50..81462c43cc4 100644 --- a/zebra-chain/src/orchard_zsa/burn.rs +++ b/zebra-chain/src/orchard_zsa/burn.rs @@ -40,7 +40,7 @@ const BURN_ITEM_SIZE: u64 = ASSET_BASE_SIZE + AMOUNT_SIZE; // FIXME: Define BurnItem (or, even Burn/NoBurn) in Orchard and reuse it here? /// Orchard ZSA burn item. #[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct BurnItem(AssetBase, u64); +pub struct BurnItem(AssetBase, NoteValue); impl BurnItem { /// Returns [`AssetBase`] being burned. @@ -48,18 +48,16 @@ impl BurnItem { self.0 } - /// Returns [`u64`] representing amount being burned. - pub fn amount(&self) -> u64 { - self.1 + /// Returns the raw [`u64`] amount being burned. + pub fn raw_amount(&self) -> u64 { + self.1.inner() } } // Convert from burn item type used in `orchard` crate -impl TryFrom<(AssetBase, NoteValue)> for BurnItem { - type Error = crate::amount::Error; - - fn try_from(item: (AssetBase, NoteValue)) -> Result { - Ok(Self(item.0, item.1.inner())) +impl From<(AssetBase, NoteValue)> for BurnItem { + fn from(item: (AssetBase, NoteValue)) -> Self { + Self(item.0, item.1) } } @@ -68,7 +66,7 @@ impl ZcashSerialize for BurnItem { let BurnItem(asset_base, amount) = self; asset_base.zcash_serialize(&mut writer)?; - writer.write_all(&amount.to_le_bytes())?; + writer.write_all(&amount.to_bytes())?; Ok(()) } @@ -79,7 +77,7 @@ impl ZcashDeserialize for BurnItem { let asset_base = AssetBase::zcash_deserialize(&mut reader)?; let mut amount_bytes = [0; 8]; reader.read_exact(&mut amount_bytes)?; - Ok(Self(asset_base, u64::from_le_bytes(amount_bytes))) + Ok(Self(asset_base, NoteValue::from_bytes(amount_bytes))) } } @@ -98,7 +96,7 @@ impl serde::Serialize for BurnItem { S: serde::Serializer, { // FIXME: return a custom error with a meaningful description? - (self.0.to_bytes(), &self.1).serialize(serializer) + (self.0.to_bytes(), &self.1.inner()).serialize(serializer) } } @@ -114,7 +112,7 @@ impl<'de> serde::Deserialize<'de> for BurnItem { // FIXME: duplicates the body of AssetBase::zcash_deserialize? Option::from(AssetBase::from_bytes(&asset_base_bytes)) .ok_or_else(|| serde::de::Error::custom("Invalid orchard_zsa AssetBase"))?, - amount, + NoteValue::from_raw(amount), )) } } @@ -170,6 +168,7 @@ impl From for ValueCommitment { pallas::Scalar::zero(), // FIXME: consider to use TryFrom and return an error instead of using "expect" amount + .inner() .try_into() .expect("should convert Burn amount to i64"), &asset, diff --git a/zebra-state/src/service/check/issuance.rs b/zebra-state/src/service/check/issuance.rs index 854f972a13b..c54febef437 100644 --- a/zebra-state/src/service/check/issuance.rs +++ b/zebra-state/src/service/check/issuance.rs @@ -47,7 +47,7 @@ pub fn valid_burns_and_issuance( // through the check it total_supply < burn.amount (the total supply is zero if the // asset is not issued). May be validation functions from the orcharfd crate need to be // reused in a some way? - if asset_state.total_supply < burn.amount() { + if asset_state.total_supply < burn.raw_amount() { return Err(ValidateContextError::InvalidBurn); } else { // Any burned asset bases in the transaction will also be present in the issued assets change, From 4932495e3601583716c51a8055aacb9eff7dceb6 Mon Sep 17 00:00:00 2001 From: Dmitry Demin Date: Wed, 27 Nov 2024 15:00:00 +0100 Subject: [PATCH 18/35] Use BurnItem::from instead of try_from --- zebra-chain/src/orchard_zsa/arbitrary.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/zebra-chain/src/orchard_zsa/arbitrary.rs b/zebra-chain/src/orchard_zsa/arbitrary.rs index 0dc89ce7080..6985336e294 100644 --- a/zebra-chain/src/orchard_zsa/arbitrary.rs +++ b/zebra-chain/src/orchard_zsa/arbitrary.rs @@ -22,11 +22,7 @@ impl Arbitrary for BurnItem { // FIXME: consider to use BurnItem(asset_base, value.try_into().expect("Invalid value for Amount")) // instead of filtering non-convertable values // FIXME: should we filter/protect from including native assets into burn here? - BundleArb::::arb_asset_to_burn() - .prop_filter_map("Conversion to Amount failed", |(asset_base, value)| { - BurnItem::try_from((asset_base, value)).ok() - }) - .boxed() + BundleArb::::arb_asset_to_burn().boxed() } type Strategy = BoxedStrategy; From 89be470a8a13f317651e4e882b2c1c23f17b5a86 Mon Sep 17 00:00:00 2001 From: Dmitry Demin Date: Wed, 27 Nov 2024 15:20:45 +0100 Subject: [PATCH 19/35] Fix a compilation error for the previous commit ('Use BurnItem::from instead of try_from') --- zebra-chain/src/orchard_zsa/arbitrary.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/zebra-chain/src/orchard_zsa/arbitrary.rs b/zebra-chain/src/orchard_zsa/arbitrary.rs index 6985336e294..46a746b9be5 100644 --- a/zebra-chain/src/orchard_zsa/arbitrary.rs +++ b/zebra-chain/src/orchard_zsa/arbitrary.rs @@ -19,10 +19,8 @@ impl Arbitrary for BurnItem { // FIXME: move arb_asset_to_burn out of BundleArb in orchard // as it does not depend on flavor (we pinned it here OrchardVanilla // just for certainty, as there's no difference, which flavor to use) - // FIXME: consider to use BurnItem(asset_base, value.try_into().expect("Invalid value for Amount")) - // instead of filtering non-convertable values // FIXME: should we filter/protect from including native assets into burn here? - BundleArb::::arb_asset_to_burn().boxed() + BundleArb::::arb_asset_to_burn() } type Strategy = BoxedStrategy; From c3daec999cd0873faf3ea6083c8ed835fe6a22bd Mon Sep 17 00:00:00 2001 From: Dmitry Demin Date: Wed, 27 Nov 2024 15:42:31 +0100 Subject: [PATCH 20/35] Fix a compilation error for the previous commit ('Use BurnItem::from instead of try_from') (2) --- zebra-chain/src/orchard_zsa/arbitrary.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zebra-chain/src/orchard_zsa/arbitrary.rs b/zebra-chain/src/orchard_zsa/arbitrary.rs index 46a746b9be5..f082c508025 100644 --- a/zebra-chain/src/orchard_zsa/arbitrary.rs +++ b/zebra-chain/src/orchard_zsa/arbitrary.rs @@ -21,6 +21,8 @@ impl Arbitrary for BurnItem { // just for certainty, as there's no difference, which flavor to use) // FIXME: should we filter/protect from including native assets into burn here? BundleArb::::arb_asset_to_burn() + .prop_map(|(asset_base, value)| BurnItem::from((asset_base, value))) + .boxed() } type Strategy = BoxedStrategy; From a8668d6c4b7bbdd98008663cace45d529ea3f1d2 Mon Sep 17 00:00:00 2001 From: Dmitry Demin Date: Tue, 3 Dec 2024 15:48:33 +0100 Subject: [PATCH 21/35] Modify ValueCommitment::with_asset to accept value as a NoteValue instead of amount (with_asset is used to process orchard burn) - this allows avoiding the use of try_into for burn in binding_verification_key function --- zebra-chain/src/orchard/commitment.rs | 6 +++--- zebra-chain/src/orchard_zsa/burn.rs | 16 ++++------------ 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/zebra-chain/src/orchard/commitment.rs b/zebra-chain/src/orchard/commitment.rs index 2e6ea3ed32d..0724a2e9d1b 100644 --- a/zebra-chain/src/orchard/commitment.rs +++ b/zebra-chain/src/orchard/commitment.rs @@ -14,7 +14,7 @@ use halo2::{ use lazy_static::lazy_static; use rand_core::{CryptoRng, RngCore}; -use orchard::note::AssetBase; +use orchard::{note::AssetBase, value::NoteValue}; use crate::{ amount::Amount, @@ -255,8 +255,8 @@ impl ValueCommitment { /// Generate a new `ValueCommitment` from an existing `rcv on a `value` (ZSA version). #[cfg(feature = "tx-v6")] #[allow(non_snake_case)] - pub fn with_asset(rcv: pallas::Scalar, value: Amount, asset: &AssetBase) -> Self { - let v = pallas::Scalar::from(value); + pub fn with_asset(rcv: pallas::Scalar, value: NoteValue, asset: &AssetBase) -> Self { + let v = pallas::Scalar::from(value.inner()); let V_zsa = asset.cv_base(); Self::from(V_zsa * v + *R * rcv) } diff --git a/zebra-chain/src/orchard_zsa/burn.rs b/zebra-chain/src/orchard_zsa/burn.rs index 81462c43cc4..6fdda20b1bc 100644 --- a/zebra-chain/src/orchard_zsa/burn.rs +++ b/zebra-chain/src/orchard_zsa/burn.rs @@ -4,8 +4,9 @@ use std::io; use halo2::pasta::pallas; +use group::prime::PrimeCurveAffine; + use crate::{ - amount::Amount, block::MAX_BLOCK_BYTES, orchard::ValueCommitment, serialization::{ @@ -125,8 +126,7 @@ pub struct NoBurn; impl From for ValueCommitment { fn from(_burn: NoBurn) -> ValueCommitment { - // FIXME: is there a simpler way to get zero ValueCommitment? - ValueCommitment::new(pallas::Scalar::zero(), Amount::zero()) + ValueCommitment(pallas::Affine::identity()) } } @@ -164,15 +164,7 @@ impl From for ValueCommitment { burn.0 .into_iter() .map(|BurnItem(asset, amount)| { - ValueCommitment::with_asset( - pallas::Scalar::zero(), - // FIXME: consider to use TryFrom and return an error instead of using "expect" - amount - .inner() - .try_into() - .expect("should convert Burn amount to i64"), - &asset, - ) + ValueCommitment::with_asset(pallas::Scalar::zero(), amount, &asset) }) .sum() } From e31f24c0fba1f737d068ef78b8e81f96ce7f6581 Mon Sep 17 00:00:00 2001 From: Arya Date: Thu, 28 Nov 2024 16:41:23 -0500 Subject: [PATCH 22/35] Adds TODOs --- zebra-chain/src/orchard_zsa/asset_state.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/zebra-chain/src/orchard_zsa/asset_state.rs b/zebra-chain/src/orchard_zsa/asset_state.rs index bdb6cd0e21b..8b24a91341f 100644 --- a/zebra-chain/src/orchard_zsa/asset_state.rs +++ b/zebra-chain/src/orchard_zsa/asset_state.rs @@ -12,6 +12,11 @@ use crate::transaction::Transaction; use super::BurnItem; +// TODO: +// - Add state request/response variants for querying asset states +// - Add RPC method for querying asset states +// - Resolve new FIXMEs related to issued asset states + /// The circulating supply and whether that supply has been finalized. #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] pub struct AssetState { From 2a5aebd4350293f118804ae4b291a09da33700fb Mon Sep 17 00:00:00 2001 From: Arya Date: Thu, 28 Nov 2024 17:58:52 -0500 Subject: [PATCH 23/35] Adds state request/response variants for querying asset states --- zebra-chain/src/orchard_zsa/asset_state.rs | 1 - zebra-state/src/request.rs | 15 +++++++++++- zebra-state/src/response.rs | 11 ++++++++- zebra-state/src/service.rs | 24 +++++++++++++++++++ zebra-state/src/service/check/issuance.rs | 28 ++++++++++------------ zebra-state/src/service/read.rs | 4 ++-- zebra-state/src/service/read/find.rs | 11 +++++++++ 7 files changed, 74 insertions(+), 20 deletions(-) diff --git a/zebra-chain/src/orchard_zsa/asset_state.rs b/zebra-chain/src/orchard_zsa/asset_state.rs index 8b24a91341f..a7a4ffb6f14 100644 --- a/zebra-chain/src/orchard_zsa/asset_state.rs +++ b/zebra-chain/src/orchard_zsa/asset_state.rs @@ -13,7 +13,6 @@ use crate::transaction::Transaction; use super::BurnItem; // TODO: -// - Add state request/response variants for querying asset states // - Add RPC method for querying asset states // - Resolve new FIXMEs related to issued asset states diff --git a/zebra-state/src/request.rs b/zebra-state/src/request.rs index 0cfa791001c..ae223802131 100644 --- a/zebra-state/src/request.rs +++ b/zebra-state/src/request.rs @@ -11,7 +11,7 @@ use zebra_chain::{ block::{self, Block}, history_tree::HistoryTree, orchard, - orchard_zsa::{IssuedAssets, IssuedAssetsChange}, + orchard_zsa::{AssetBase, IssuedAssets, IssuedAssetsChange}, parallel::tree::NoteCommitmentTrees, sapling, serialization::SerializationError, @@ -1122,6 +1122,17 @@ pub enum ReadRequest { /// Returns [`ReadResponse::TipBlockSize(usize)`](ReadResponse::TipBlockSize) /// with the current best chain tip block size in bytes. TipBlockSize, + + #[cfg(feature = "tx-v6")] + /// Returns [`ReadResponse::AssetState`] with an [`AssetState`](zebra_chain::orchard_zsa::AssetState) + /// of the provided [`AssetBase`] if it exists for the best chain tip or finalized chain tip (depending + /// on the `include_non_finalized` flag). + AssetState { + /// The [`AssetBase`] to return the asset state for. + asset_base: AssetBase, + /// Whether to include the issued asset state changes in the non-finalized state. + include_non_finalized: bool, + }, } impl ReadRequest { @@ -1159,6 +1170,8 @@ impl ReadRequest { ReadRequest::CheckBlockProposalValidity(_) => "check_block_proposal_validity", #[cfg(feature = "getblocktemplate-rpcs")] ReadRequest::TipBlockSize => "tip_block_size", + #[cfg(feature = "tx-v6")] + ReadRequest::AssetState { .. } => "asset_state", } } diff --git a/zebra-state/src/response.rs b/zebra-state/src/response.rs index 77c252b0c75..4372832a231 100644 --- a/zebra-state/src/response.rs +++ b/zebra-state/src/response.rs @@ -5,7 +5,9 @@ use std::{collections::BTreeMap, sync::Arc}; use zebra_chain::{ amount::{Amount, NonNegative}, block::{self, Block}, - orchard, sapling, + orchard, + orchard_zsa::AssetState, + sapling, serialization::DateTime32, subtree::{NoteCommitmentSubtreeData, NoteCommitmentSubtreeIndex}, transaction::{self, Transaction}, @@ -233,6 +235,10 @@ pub enum ReadResponse { #[cfg(feature = "getblocktemplate-rpcs")] /// Response to [`ReadRequest::TipBlockSize`] TipBlockSize(Option), + + #[cfg(feature = "tx-v6")] + /// Response to [`ReadRequest::AssetState`] + AssetState(Option), } /// A structure with the information needed from the state to build a `getblocktemplate` RPC response. @@ -322,6 +328,9 @@ impl TryFrom for Response { ReadResponse::ChainInfo(_) | ReadResponse::SolutionRate(_) | ReadResponse::TipBlockSize(_) => { Err("there is no corresponding Response for this ReadResponse") } + + #[cfg(feature = "tx-v6")] + ReadResponse::AssetState(_) => Err("there is no corresponding Response for this ReadResponse"), } } } diff --git a/zebra-state/src/service.rs b/zebra-state/src/service.rs index adc61f887ae..4f17950a312 100644 --- a/zebra-state/src/service.rs +++ b/zebra-state/src/service.rs @@ -1947,6 +1947,30 @@ impl Service for ReadStateService { }) .wait_for_panics() } + + #[cfg(feature = "tx-v6")] + ReadRequest::AssetState { + asset_base, + include_non_finalized, + } => { + let state = self.clone(); + + tokio::task::spawn_blocking(move || { + span.in_scope(move || { + let best_chain = include_non_finalized + .then(|| state.latest_best_chain()) + .flatten(); + + let response = read::asset_state(best_chain, &state.db, &asset_base); + + // The work is done in the future. + timer.finish(module_path!(), line!(), "ReadRequest::AssetState"); + + Ok(ReadResponse::AssetState(response)) + }) + }) + .wait_for_panics() + } } } } diff --git a/zebra-state/src/service/check/issuance.rs b/zebra-state/src/service/check/issuance.rs index c54febef437..c4b68eb202d 100644 --- a/zebra-state/src/service/check/issuance.rs +++ b/zebra-state/src/service/check/issuance.rs @@ -4,24 +4,10 @@ use std::{collections::HashMap, sync::Arc}; use zebra_chain::orchard_zsa::{AssetBase, AssetState, IssuedAssets}; -use crate::{SemanticallyVerifiedBlock, ValidateContextError, ZebraDb}; +use crate::{service::read, SemanticallyVerifiedBlock, ValidateContextError, ZebraDb}; use super::Chain; -// TODO: Factor out chain/disk read to a fn in the `read` module. -fn asset_state( - finalized_state: &ZebraDb, - parent_chain: &Arc, - issued_assets: &HashMap, - asset_base: &AssetBase, -) -> Option { - issued_assets - .get(asset_base) - .copied() - .or_else(|| parent_chain.issued_asset(asset_base)) - .or_else(|| finalized_state.issued_asset(asset_base)) -} - pub fn valid_burns_and_issuance( finalized_state: &ZebraDb, parent_chain: &Arc, @@ -84,3 +70,15 @@ pub fn valid_burns_and_issuance( Ok(issued_assets.into()) } + +fn asset_state( + finalized_state: &ZebraDb, + parent_chain: &Arc, + issued_assets: &HashMap, + asset_base: &AssetBase, +) -> Option { + issued_assets + .get(asset_base) + .copied() + .or_else(|| read::asset_state(Some(parent_chain), finalized_state, asset_base)) +} diff --git a/zebra-state/src/service/read.rs b/zebra-state/src/service/read.rs index 0188ca1bf5e..f2aa2f9adf8 100644 --- a/zebra-state/src/service/read.rs +++ b/zebra-state/src/service/read.rs @@ -34,8 +34,8 @@ pub use block::{ any_utxo, block, block_header, mined_transaction, transaction_hashes_for_block, unspent_utxo, }; pub use find::{ - best_tip, block_locator, depth, finalized_state_contains_block_hash, find_chain_hashes, - find_chain_headers, hash_by_height, height_by_hash, next_median_time_past, + asset_state, best_tip, block_locator, depth, finalized_state_contains_block_hash, + find_chain_hashes, find_chain_headers, hash_by_height, height_by_hash, next_median_time_past, non_finalized_state_contains_block_hash, tip, tip_height, tip_with_value_balance, }; pub use tree::{orchard_subtrees, orchard_tree, sapling_subtrees, sapling_tree}; diff --git a/zebra-state/src/service/read/find.rs b/zebra-state/src/service/read/find.rs index e9d557dbfb2..74347e61d04 100644 --- a/zebra-state/src/service/read/find.rs +++ b/zebra-state/src/service/read/find.rs @@ -21,6 +21,7 @@ use chrono::{DateTime, Utc}; use zebra_chain::{ amount::NonNegative, block::{self, Block, Height}, + orchard_zsa::{AssetBase, AssetState}, serialization::DateTime32, value_balance::ValueBalance, }; @@ -679,3 +680,13 @@ pub(crate) fn calculate_median_time_past(relevant_chain: Vec>) -> Dat DateTime32::try_from(median_time_past).expect("valid blocks have in-range times") } + +/// Return the [`AssetState`] for the provided [`AssetBase`], if it exists in the provided chain. +pub fn asset_state(chain: Option, db: &ZebraDb, asset_base: &AssetBase) -> Option +where + C: AsRef, +{ + chain + .and_then(|chain| chain.as_ref().issued_asset(asset_base)) + .or_else(|| db.issued_asset(asset_base)) +} From f7b43a935e2cfa3f3b37eaf83b3d743eca4c70b9 Mon Sep 17 00:00:00 2001 From: Arya Date: Thu, 28 Nov 2024 18:15:56 -0500 Subject: [PATCH 24/35] Adds a `getassetstate` RPC method --- zebra-chain/src/orchard_zsa/asset_state.rs | 3 +- zebra-rpc/src/methods.rs | 43 +++++++++++++++++++++- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/zebra-chain/src/orchard_zsa/asset_state.rs b/zebra-chain/src/orchard_zsa/asset_state.rs index a7a4ffb6f14..f3a7f569a57 100644 --- a/zebra-chain/src/orchard_zsa/asset_state.rs +++ b/zebra-chain/src/orchard_zsa/asset_state.rs @@ -13,11 +13,10 @@ use crate::transaction::Transaction; use super::BurnItem; // TODO: -// - Add RPC method for querying asset states // - Resolve new FIXMEs related to issued asset states /// The circulating supply and whether that supply has been finalized. -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, serde::Serialize)] pub struct AssetState { /// Indicates whether the asset is finalized such that no more of it can be issued. pub is_finalized: bool, diff --git a/zebra-rpc/src/methods.rs b/zebra-rpc/src/methods.rs index 8becc5bb79c..f38a6e4108f 100644 --- a/zebra-rpc/src/methods.rs +++ b/zebra-rpc/src/methods.rs @@ -23,7 +23,7 @@ use zebra_chain::{ block::{self, Height, SerializedBlock}, chain_tip::{ChainTip, NetworkChainTipHeightEstimator}, parameters::{ConsensusBranchId, Network, NetworkUpgrade}, - serialization::ZcashDeserialize, + serialization::{ZcashDeserialize, ZcashDeserializeInto}, subtree::NoteCommitmentSubtreeIndex, transaction::{self, SerializedTransaction, Transaction, UnminedTx}, transparent::{self, Address}, @@ -302,6 +302,17 @@ pub trait Rpc { address_strings: AddressStrings, ) -> BoxFuture>>; + /// Returns the asset state of the provided asset base at the best chain tip or finalized chain tip. + /// + /// method: post + /// tags: blockchain + #[rpc(name = "getassetstate")] + fn get_asset_state( + &self, + asset_base: String, + include_non_finalized: Option, + ) -> BoxFuture>; + /// Stop the running zebrad process. /// /// # Notes @@ -1358,6 +1369,36 @@ where .boxed() } + fn get_asset_state( + &self, + asset_base: String, + include_non_finalized: Option, + ) -> BoxFuture> { + let state = self.state.clone(); + let include_non_finalized = include_non_finalized.unwrap_or(true); + + async move { + let asset_base = hex::decode(asset_base) + .map_server_error()? + .zcash_deserialize_into() + .map_server_error()?; + + let request = zebra_state::ReadRequest::AssetState { + asset_base, + include_non_finalized, + }; + + let zebra_state::ReadResponse::AssetState(asset_state) = + state.oneshot(request).await.map_server_error()? + else { + unreachable!("unexpected response from state service"); + }; + + asset_state.ok_or_server_error("asset base not found") + } + .boxed() + } + fn stop(&self) -> Result { #[cfg(not(target_os = "windows"))] if self.network.is_regtest() { From e35ae57b051e254c398d5a44f5173c06e5361bf8 Mon Sep 17 00:00:00 2001 From: Arya Date: Thu, 28 Nov 2024 18:59:39 -0500 Subject: [PATCH 25/35] Adds snapshot test --- Cargo.lock | 1 + zebra-chain/Cargo.toml | 2 ++ zebra-chain/src/orchard_zsa.rs | 2 +- zebra-chain/src/orchard_zsa/asset_state.rs | 27 +++++++++++++- zebra-rpc/src/methods/tests/snapshot.rs | 36 +++++++++++++++++++ .../snapshots/get_asset_state@mainnet.snap | 8 +++++ .../snapshots/get_asset_state@testnet.snap | 8 +++++ .../get_asset_state_not_found@mainnet.snap | 8 +++++ .../get_asset_state_not_found@testnet.snap | 8 +++++ 9 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 zebra-rpc/src/methods/tests/snapshots/get_asset_state@mainnet.snap create mode 100644 zebra-rpc/src/methods/tests/snapshots/get_asset_state@testnet.snap create mode 100644 zebra-rpc/src/methods/tests/snapshots/get_asset_state_not_found@mainnet.snap create mode 100644 zebra-rpc/src/methods/tests/snapshots/get_asset_state_not_found@testnet.snap diff --git a/Cargo.lock b/Cargo.lock index cad04052478..62b4bf0d7c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6034,6 +6034,7 @@ dependencies = [ "incrementalmerkletree", "itertools 0.13.0", "jubjub", + "k256", "lazy_static", "nonempty", "num-integer", diff --git a/zebra-chain/Cargo.toml b/zebra-chain/Cargo.toml index 82cc28f3045..96469b0fb65 100644 --- a/zebra-chain/Cargo.toml +++ b/zebra-chain/Cargo.toml @@ -157,6 +157,8 @@ rand_chacha = { version = "0.3.1", optional = true } zebra-test = { path = "../zebra-test/", version = "1.0.0-beta.41", optional = true } +k256 = "0.13.3" + [dev-dependencies] # Benchmarks criterion = { version = "0.5.1", features = ["html_reports"] } diff --git a/zebra-chain/src/orchard_zsa.rs b/zebra-chain/src/orchard_zsa.rs index 5779da32e01..00a27360742 100644 --- a/zebra-chain/src/orchard_zsa.rs +++ b/zebra-chain/src/orchard_zsa.rs @@ -9,7 +9,7 @@ pub(crate) mod arbitrary; #[cfg(any(test, feature = "proptest-impl"))] pub mod tests; -mod asset_state; +pub mod asset_state; mod burn; mod issuance; diff --git a/zebra-chain/src/orchard_zsa/asset_state.rs b/zebra-chain/src/orchard_zsa/asset_state.rs index f3a7f569a57..10dc3af1e9f 100644 --- a/zebra-chain/src/orchard_zsa/asset_state.rs +++ b/zebra-chain/src/orchard_zsa/asset_state.rs @@ -8,7 +8,7 @@ use std::{ use orchard::issuance::IssueAction; pub use orchard::note::AssetBase; -use crate::transaction::Transaction; +use crate::{serialization::ZcashSerialize, transaction::Transaction}; use super::BurnItem; @@ -377,3 +377,28 @@ impl std::ops::Add for IssuedAssetsChange { } } } +/// Used in snapshot test for `getassetstate` RPC method. +// TODO: Replace with `AssetBase::random()` or a known value. +pub trait RandomAssetBase { + /// Generates a ZSA random asset. + /// + /// This is only used in tests. + fn random_serialized() -> String; +} + +impl RandomAssetBase for AssetBase { + fn random_serialized() -> String { + let isk = orchard::keys::IssuanceAuthorizingKey::from_bytes( + k256::NonZeroScalar::random(&mut rand_core::OsRng) + .to_bytes() + .into(), + ) + .unwrap(); + let ik = orchard::keys::IssuanceValidatingKey::from(&isk); + let asset_descr = b"zsa_asset".to_vec(); + AssetBase::derive(&ik, &asset_descr) + .zcash_serialize_to_vec() + .map(hex::encode) + .expect("random asset base should serialize") + } +} diff --git a/zebra-rpc/src/methods/tests/snapshot.rs b/zebra-rpc/src/methods/tests/snapshot.rs index f4d7804088e..fe9e9cccd7f 100644 --- a/zebra-rpc/src/methods/tests/snapshot.rs +++ b/zebra-rpc/src/methods/tests/snapshot.rs @@ -14,6 +14,7 @@ use zebra_chain::{ block::Block, chain_tip::mock::MockChainTip, orchard, + orchard_zsa::{asset_state::RandomAssetBase, AssetBase, AssetState}, parameters::{ subsidy::POST_NU6_FUNDING_STREAMS_TESTNET, testnet::{self, ConfiguredActivationHeights, Parameters}, @@ -536,6 +537,41 @@ async fn test_mocked_rpc_response_data_for_network(network: &Network) { settings.bind(|| { insta::assert_json_snapshot!(format!("z_get_subtrees_by_index_for_orchard"), subtrees) }); + + // Test the response format from `getassetstate`. + + // Prepare the state response and make the RPC request. + let rsp = state + .expect_request_that(|req| matches!(req, ReadRequest::AssetState { .. })) + .map(|responder| responder.respond(ReadResponse::AssetState(None))); + let req = rpc.get_asset_state(AssetBase::random_serialized(), None); + + // Get the RPC error response. + let (asset_state_rsp, ..) = tokio::join!(req, rsp); + let asset_state = asset_state_rsp.expect_err("The RPC response should be an error"); + + // Check the error response. + settings + .bind(|| insta::assert_json_snapshot!(format!("get_asset_state_not_found"), asset_state)); + + // Prepare the state response and make the RPC request. + let rsp = state + .expect_request_that(|req| matches!(req, ReadRequest::AssetState { .. })) + .map(|responder| { + responder.respond(ReadResponse::AssetState(Some(AssetState { + is_finalized: true, + total_supply: 1000, + }))) + }); + let req = rpc.get_asset_state(AssetBase::random_serialized(), None); + + // Get the RPC response. + let (asset_state_rsp, ..) = tokio::join!(req, rsp); + let asset_state = + asset_state_rsp.expect("The RPC response should contain a `AssetState` struct."); + + // Check the response. + settings.bind(|| insta::assert_json_snapshot!(format!("get_asset_state"), asset_state)); } /// Snapshot `getinfo` response, using `cargo insta` and JSON serialization. diff --git a/zebra-rpc/src/methods/tests/snapshots/get_asset_state@mainnet.snap b/zebra-rpc/src/methods/tests/snapshots/get_asset_state@mainnet.snap new file mode 100644 index 00000000000..9085ab62c88 --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshots/get_asset_state@mainnet.snap @@ -0,0 +1,8 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot.rs +expression: asset_state +--- +{ + "is_finalized": true, + "total_supply": 1000 +} diff --git a/zebra-rpc/src/methods/tests/snapshots/get_asset_state@testnet.snap b/zebra-rpc/src/methods/tests/snapshots/get_asset_state@testnet.snap new file mode 100644 index 00000000000..9085ab62c88 --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshots/get_asset_state@testnet.snap @@ -0,0 +1,8 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot.rs +expression: asset_state +--- +{ + "is_finalized": true, + "total_supply": 1000 +} diff --git a/zebra-rpc/src/methods/tests/snapshots/get_asset_state_not_found@mainnet.snap b/zebra-rpc/src/methods/tests/snapshots/get_asset_state_not_found@mainnet.snap new file mode 100644 index 00000000000..9efcfd5868f --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshots/get_asset_state_not_found@mainnet.snap @@ -0,0 +1,8 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot.rs +expression: asset_state +--- +{ + "code": 0, + "message": "asset base not found" +} diff --git a/zebra-rpc/src/methods/tests/snapshots/get_asset_state_not_found@testnet.snap b/zebra-rpc/src/methods/tests/snapshots/get_asset_state_not_found@testnet.snap new file mode 100644 index 00000000000..9efcfd5868f --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshots/get_asset_state_not_found@testnet.snap @@ -0,0 +1,8 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot.rs +expression: asset_state +--- +{ + "code": 0, + "message": "asset base not found" +} From c278758fac9ca4ec2843ecbbf7144ae590265f2a Mon Sep 17 00:00:00 2001 From: Arya Date: Thu, 28 Nov 2024 19:41:38 -0500 Subject: [PATCH 26/35] Addesses some FIXMEs and replaces a couple others with TODOs. --- zebra-chain/src/orchard_zsa/asset_state.rs | 7 +----- zebra-state/src/service/check/issuance.rs | 27 +++++++--------------- 2 files changed, 9 insertions(+), 25 deletions(-) diff --git a/zebra-chain/src/orchard_zsa/asset_state.rs b/zebra-chain/src/orchard_zsa/asset_state.rs index 10dc3af1e9f..1ecf4a76091 100644 --- a/zebra-chain/src/orchard_zsa/asset_state.rs +++ b/zebra-chain/src/orchard_zsa/asset_state.rs @@ -12,9 +12,6 @@ use crate::{serialization::ZcashSerialize, transaction::Transaction}; use super::BurnItem; -// TODO: -// - Resolve new FIXMEs related to issued asset states - /// The circulating supply and whether that supply has been finalized. #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, serde::Serialize)] pub struct AssetState { @@ -31,8 +28,7 @@ pub struct AssetState { pub struct AssetStateChange { /// Whether the asset should be finalized such that no more of it can be issued. pub should_finalize: bool, - // FIXME: is this a correct comment? - /// Whether the asset should be finalized such that no more of it can be issued. + /// Whether the asset has been issued in this change. pub includes_issuance: bool, /// The change in supply from newly issued assets or burned assets, if any. pub supply_change: SupplyChange, @@ -214,7 +210,6 @@ impl AssetStateChange { }) .unwrap_or_default() .into_iter() - // FIXME: We use 0 as a value - is that correct? .map(|asset_base| Self::new(asset_base, SupplyChange::Issuance(0), true)); supply_changes.chain(finalize_changes) diff --git a/zebra-state/src/service/check/issuance.rs b/zebra-state/src/service/check/issuance.rs index c4b68eb202d..98df3c4ed9f 100644 --- a/zebra-state/src/service/check/issuance.rs +++ b/zebra-state/src/service/check/issuance.rs @@ -15,8 +15,8 @@ pub fn valid_burns_and_issuance( ) -> Result { let mut issued_assets = HashMap::new(); - // FIXME: Do all checks (for duplication, existence etc.) need to be performed per tranaction, not per - // the entire block? + // Burns need to be checked and asset state changes need to be applied per tranaction, in case + // the asset being burned was also issued in an earlier transaction in the same block. for (issued_assets_change, transaction) in semantically_verified .issued_assets_changes .iter() @@ -27,12 +27,10 @@ pub fn valid_burns_and_issuance( let asset_base = burn.asset(); let asset_state = asset_state(finalized_state, parent_chain, &issued_assets, &asset_base) + // The asset being burned should have been issued by a previous transaction, and + // any assets issued in previous transactions should be present in the issued assets map. .ok_or(ValidateContextError::InvalidBurn)?; - // FIXME: The check that we can't burn an asset before we issued it is implicit - - // through the check it total_supply < burn.amount (the total supply is zero if the - // asset is not issued). May be validation functions from the orcharfd crate need to be - // reused in a some way? if asset_state.total_supply < burn.raw_amount() { return Err(ValidateContextError::InvalidBurn); } else { @@ -42,13 +40,8 @@ pub fn valid_burns_and_issuance( } } - // FIXME: Not sure: it looks like semantically_verified.issued_assets_changes is already - // filled with burn and issuance items in zebra-consensus, see Verifier::call function in - // zebra-consensus/src/transaction.rs (it uses from_burn and from_issue_action AssetStateChange - // methods from ebra-chain/src/orchard_zsa/asset_state.rs). Can't it cause a duplication? - // Can we collect all change items here, not in zebra-consensus (and so we don't need - // SemanticallyVerifiedBlock::issued_assets_changes at all), or performing part of the - // checks in zebra-consensus is important for the consensus checks order in a some way? + // TODO: Remove the `issued_assets_change` field from `SemanticallyVerifiedBlock` and get the changes + // directly from transactions here and when writing blocks to disk. for (asset_base, change) in issued_assets_change.iter() { let asset_state = asset_state(finalized_state, parent_chain, &issued_assets, &asset_base) @@ -58,12 +51,8 @@ pub fn valid_burns_and_issuance( .apply_change(change) .ok_or(ValidateContextError::InvalidIssuance)?; - // FIXME: Is it correct to do nothing if the issued_assets aready has asset_base? Now it'd be - // replaced with updated_asset_state in this case (where the duplicated value is added to - // the supply). Block may have two burn records for the same asset but in different - // transactions - it's allowed, that's why the check has been removed. On the other - // hand, there needs to be a check that denies duplicated burn records for the same - // asset inside the same transaction. + // TODO: Update `Burn` to `HashMap)` and return an error during deserialization if + // any asset base is burned twice in the same transaction issued_assets.insert(asset_base, updated_asset_state); } } From d144774f722e67e358dc16c86b9680c9778214d6 Mon Sep 17 00:00:00 2001 From: Arya Date: Thu, 28 Nov 2024 21:14:57 -0500 Subject: [PATCH 27/35] Removes `issued_assets_change` field from `SemanticallyVerifiedBlock` --- zebra-chain/src/orchard_zsa/asset_state.rs | 11 +++++ zebra-consensus/src/block.rs | 11 ++--- zebra-consensus/src/checkpoint.rs | 5 +-- zebra-consensus/src/error.rs | 3 -- zebra-consensus/src/transaction.rs | 6 --- zebra-state/src/arbitrary.rs | 5 --- zebra-state/src/request.rs | 42 ++++--------------- zebra-state/src/service/chain_tip.rs | 1 - zebra-state/src/service/check/issuance.rs | 11 +++-- .../zebra_db/block/tests/vectors.rs | 5 --- .../finalized_state/zebra_db/shielded.rs | 29 +++++++------ 11 files changed, 45 insertions(+), 84 deletions(-) diff --git a/zebra-chain/src/orchard_zsa/asset_state.rs b/zebra-chain/src/orchard_zsa/asset_state.rs index 1ecf4a76091..264951bfd52 100644 --- a/zebra-chain/src/orchard_zsa/asset_state.rs +++ b/zebra-chain/src/orchard_zsa/asset_state.rs @@ -372,6 +372,17 @@ impl std::ops::Add for IssuedAssetsChange { } } } + +impl From> for IssuedAssetsChange { + fn from(change: Arc<[IssuedAssetsChange]>) -> Self { + change + .iter() + .cloned() + .reduce(|a, b| a + b) + .unwrap_or_default() + } +} + /// Used in snapshot test for `getassetstate` RPC method. // TODO: Replace with `AssetBase::random()` or a known value. pub trait RandomAssetBase { diff --git a/zebra-consensus/src/block.rs b/zebra-consensus/src/block.rs index aa4bf94c72f..207a202f6ea 100644 --- a/zebra-consensus/src/block.rs +++ b/zebra-consensus/src/block.rs @@ -15,7 +15,7 @@ use std::{ }; use chrono::Utc; -use futures::stream::FuturesOrdered; +use futures::stream::FuturesUnordered; use futures_util::FutureExt; use thiserror::Error; use tower::{Service, ServiceExt}; @@ -226,7 +226,7 @@ where tx::check::coinbase_outputs_are_decryptable(&coinbase_tx, &network, height)?; // Send transactions to the transaction verifier to be checked - let mut async_checks = FuturesOrdered::new(); + let mut async_checks = FuturesUnordered::new(); let known_utxos = Arc::new(transparent::new_ordered_outputs( &block, @@ -243,7 +243,7 @@ where height, time: block.header.time, }); - async_checks.push_back(rsp); + async_checks.push(rsp); } tracing::trace!(len = async_checks.len(), "built async tx checks"); @@ -252,7 +252,6 @@ where // Sum up some block totals from the transaction responses. let mut legacy_sigop_count = 0; let mut block_miner_fees = Ok(Amount::zero()); - let mut issued_assets_changes = Vec::new(); use futures::StreamExt; while let Some(result) = async_checks.next().await { @@ -261,7 +260,6 @@ where tx_id: _, miner_fee, legacy_sigop_count: tx_legacy_sigop_count, - issued_assets_change, } = result .map_err(Into::into) .map_err(VerifyBlockError::Transaction)? @@ -276,8 +274,6 @@ where if let Some(miner_fee) = miner_fee { block_miner_fees += miner_fee; } - - issued_assets_changes.push(issued_assets_change); } // Check the summed block totals @@ -327,7 +323,6 @@ where new_outputs, transaction_hashes, deferred_balance: Some(expected_deferred_amount), - issued_assets_changes: issued_assets_changes.into(), }; // Return early for proposal requests when getblocktemplate-rpcs feature is enabled diff --git a/zebra-consensus/src/checkpoint.rs b/zebra-consensus/src/checkpoint.rs index f6520ba5564..039ea6e33e3 100644 --- a/zebra-consensus/src/checkpoint.rs +++ b/zebra-consensus/src/checkpoint.rs @@ -42,7 +42,7 @@ use crate::{ Progress::{self, *}, TargetHeight::{self, *}, }, - error::{BlockError, SubsidyError, TransactionError}, + error::{BlockError, SubsidyError}, funding_stream_values, BoxError, ParameterCheckpoint as _, }; @@ -619,8 +619,7 @@ where }; // don't do precalculation until the block passes basic difficulty checks - let block = CheckpointVerifiedBlock::new(block, Some(hash), expected_deferred_amount) - .ok_or_else(|| VerifyBlockError::from(TransactionError::InvalidAssetIssuanceOrBurn))?; + let block = CheckpointVerifiedBlock::new(block, Some(hash), expected_deferred_amount); crate::block::check::merkle_root_validity( &self.network, diff --git a/zebra-consensus/src/error.rs b/zebra-consensus/src/error.rs index 9aa41103910..8fe14c62d52 100644 --- a/zebra-consensus/src/error.rs +++ b/zebra-consensus/src/error.rs @@ -239,9 +239,6 @@ pub enum TransactionError { #[error("failed to verify ZIP-317 transaction rules, transaction was not inserted to mempool")] #[cfg_attr(any(test, feature = "proptest-impl"), proptest(skip))] Zip317(#[from] zebra_chain::transaction::zip317::Error), - - #[error("failed to validate asset issuance and/or burns")] - InvalidAssetIssuanceOrBurn, } impl From for TransactionError { diff --git a/zebra-consensus/src/transaction.rs b/zebra-consensus/src/transaction.rs index ca06395b68c..8c5a6d69c92 100644 --- a/zebra-consensus/src/transaction.rs +++ b/zebra-consensus/src/transaction.rs @@ -19,7 +19,6 @@ use tracing::Instrument; use zebra_chain::{ amount::{Amount, NonNegative}, block, orchard, - orchard_zsa::IssuedAssetsChange, parameters::{Network, NetworkUpgrade}, primitives::Groth16Proof, sapling, @@ -144,10 +143,6 @@ pub enum Response { /// The number of legacy signature operations in this transaction's /// transparent inputs and outputs. legacy_sigop_count: u64, - - /// The changes to the issued assets map that should be applied for - /// this transaction. - issued_assets_change: IssuedAssetsChange, }, /// A response to a mempool transaction verification request. @@ -485,7 +480,6 @@ where tx_id, miner_fee, legacy_sigop_count, - issued_assets_change: IssuedAssetsChange::from_transaction(&tx).ok_or(TransactionError::InvalidAssetIssuanceOrBurn)?, }, Request::Mempool { transaction, .. } => { let transaction = VerifiedUnminedTx::new( diff --git a/zebra-state/src/arbitrary.rs b/zebra-state/src/arbitrary.rs index 183567b5794..352ad550159 100644 --- a/zebra-state/src/arbitrary.rs +++ b/zebra-state/src/arbitrary.rs @@ -5,7 +5,6 @@ use std::sync::Arc; use zebra_chain::{ amount::Amount, block::{self, Block}, - orchard_zsa::IssuedAssetsChange, transaction::Transaction, transparent, value_balance::ValueBalance, @@ -31,8 +30,6 @@ impl Prepare for Arc { let transaction_hashes: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect(); let new_outputs = transparent::new_ordered_outputs_with_height(&block, height, &transaction_hashes); - let issued_assets_changes = IssuedAssetsChange::from_transactions(&block.transactions) - .expect("prepared blocks should be semantically valid"); SemanticallyVerifiedBlock { block, @@ -41,7 +38,6 @@ impl Prepare for Arc { new_outputs, transaction_hashes, deferred_balance: None, - issued_assets_changes, } } } @@ -120,7 +116,6 @@ impl ContextuallyVerifiedBlock { new_outputs, transaction_hashes, deferred_balance: _, - issued_assets_changes: _, } = block.into(); Self { diff --git a/zebra-state/src/request.rs b/zebra-state/src/request.rs index ae223802131..cd71173caae 100644 --- a/zebra-state/src/request.rs +++ b/zebra-state/src/request.rs @@ -164,9 +164,6 @@ pub struct SemanticallyVerifiedBlock { pub transaction_hashes: Arc<[transaction::Hash]>, /// This block's contribution to the deferred pool. pub deferred_balance: Option>, - /// A precomputed list of the [`IssuedAssetsChange`]s for the transactions in this block, - /// in the same order as `block.transactions`. - pub issued_assets_changes: Arc<[IssuedAssetsChange]>, } /// A block ready to be committed directly to the finalized state with @@ -301,10 +298,9 @@ pub struct FinalizedBlock { pub(super) treestate: Treestate, /// This block's contribution to the deferred pool. pub(super) deferred_balance: Option>, - /// Either changes to be applied to the previous `issued_assets` map for the finalized tip, or - /// updates asset states to be inserted into the finalized state, replacing the previous + /// Updated asset states to be inserted into the finalized state, replacing the previous /// asset states for those asset bases. - pub issued_assets: IssuedAssetsOrChange, + pub issued_assets: Option, } /// Either changes to be applied to the previous `issued_assets` map for the finalized tip, or @@ -319,18 +315,6 @@ pub enum IssuedAssetsOrChange { Change(IssuedAssetsChange), } -impl From> for IssuedAssetsOrChange { - fn from(change: Arc<[IssuedAssetsChange]>) -> Self { - Self::Change( - change - .iter() - .cloned() - .reduce(|a, b| a + b) - .unwrap_or_default(), - ) - } -} - impl From for IssuedAssetsOrChange { fn from(updated_issued_assets: IssuedAssets) -> Self { Self::Updated(updated_issued_assets) @@ -340,13 +324,7 @@ impl From for IssuedAssetsOrChange { impl FinalizedBlock { /// Constructs [`FinalizedBlock`] from [`CheckpointVerifiedBlock`] and its [`Treestate`]. pub fn from_checkpoint_verified(block: CheckpointVerifiedBlock, treestate: Treestate) -> Self { - let issued_assets = block.issued_assets_changes.clone().into(); - - Self::from_semantically_verified( - SemanticallyVerifiedBlock::from(block), - treestate, - issued_assets, - ) + Self::from_semantically_verified(SemanticallyVerifiedBlock::from(block), treestate, None) } /// Constructs [`FinalizedBlock`] from [`ContextuallyVerifiedBlock`] and its [`Treestate`]. @@ -354,7 +332,7 @@ impl FinalizedBlock { block: ContextuallyVerifiedBlock, treestate: Treestate, ) -> Self { - let issued_assets = block.issued_assets.clone().into(); + let issued_assets = Some(block.issued_assets.clone()); Self::from_semantically_verified( SemanticallyVerifiedBlock::from(block), treestate, @@ -366,7 +344,7 @@ impl FinalizedBlock { fn from_semantically_verified( block: SemanticallyVerifiedBlock, treestate: Treestate, - issued_assets: IssuedAssetsOrChange, + issued_assets: Option, ) -> Self { Self { block: block.block, @@ -451,7 +429,6 @@ impl ContextuallyVerifiedBlock { new_outputs, transaction_hashes, deferred_balance, - issued_assets_changes: _, } = semantically_verified; // This is redundant for the non-finalized state, @@ -483,12 +460,10 @@ impl CheckpointVerifiedBlock { block: Arc, hash: Option, deferred_balance: Option>, - ) -> Option { - let issued_assets_change = IssuedAssetsChange::from_transactions(&block.transactions)?; + ) -> Self { let mut block = Self::with_hash(block.clone(), hash.unwrap_or(block.hash())); block.deferred_balance = deferred_balance; - block.issued_assets_changes = issued_assets_change; - Some(block) + block } /// Creates a block that's ready to be committed to the finalized state, @@ -517,7 +492,6 @@ impl SemanticallyVerifiedBlock { new_outputs, transaction_hashes, deferred_balance: None, - issued_assets_changes: Arc::new([]), } } @@ -550,7 +524,6 @@ impl From> for SemanticallyVerifiedBlock { new_outputs, transaction_hashes, deferred_balance: None, - issued_assets_changes: Arc::new([]), } } } @@ -570,7 +543,6 @@ impl From for SemanticallyVerifiedBlock { .constrain::() .expect("deferred balance in a block must me non-negative"), ), - issued_assets_changes: Arc::new([]), } } } diff --git a/zebra-state/src/service/chain_tip.rs b/zebra-state/src/service/chain_tip.rs index 8a0ed517766..04ea61d6982 100644 --- a/zebra-state/src/service/chain_tip.rs +++ b/zebra-state/src/service/chain_tip.rs @@ -116,7 +116,6 @@ impl From for ChainTipBlock { new_outputs: _, transaction_hashes, deferred_balance: _, - issued_assets_changes: _, } = prepared; Self { diff --git a/zebra-state/src/service/check/issuance.rs b/zebra-state/src/service/check/issuance.rs index 98df3c4ed9f..472ffd61fd8 100644 --- a/zebra-state/src/service/check/issuance.rs +++ b/zebra-state/src/service/check/issuance.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, sync::Arc}; -use zebra_chain::orchard_zsa::{AssetBase, AssetState, IssuedAssets}; +use zebra_chain::orchard_zsa::{AssetBase, AssetState, IssuedAssets, IssuedAssetsChange}; use crate::{service::read, SemanticallyVerifiedBlock, ValidateContextError, ZebraDb}; @@ -17,11 +17,10 @@ pub fn valid_burns_and_issuance( // Burns need to be checked and asset state changes need to be applied per tranaction, in case // the asset being burned was also issued in an earlier transaction in the same block. - for (issued_assets_change, transaction) in semantically_verified - .issued_assets_changes - .iter() - .zip(&semantically_verified.block.transactions) - { + for transaction in &semantically_verified.block.transactions { + let issued_assets_change = IssuedAssetsChange::from_transaction(transaction) + .ok_or(ValidateContextError::InvalidIssuance)?; + // Check that no burn item attempts to burn more than the issued supply for an asset for burn in transaction.orchard_burns() { let asset_base = burn.asset(); diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs b/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs index d7df21fda0e..194f2202a87 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs @@ -20,7 +20,6 @@ use zebra_chain::{ }, Block, Height, }, - orchard_zsa::IssuedAssetsChange, parameters::Network::{self, *}, serialization::{ZcashDeserializeInto, ZcashSerialize}, transparent::new_ordered_outputs_with_height, @@ -130,9 +129,6 @@ fn test_block_db_round_trip_with( .collect(); let new_outputs = new_ordered_outputs_with_height(&original_block, Height(0), &transaction_hashes); - let issued_assets_changes = - IssuedAssetsChange::from_transactions(&original_block.transactions) - .expect("issued assets should be valid"); CheckpointVerifiedBlock(SemanticallyVerifiedBlock { block: original_block.clone(), @@ -141,7 +137,6 @@ fn test_block_db_round_trip_with( new_outputs, transaction_hashes, deferred_balance: None, - issued_assets_changes, }) }; 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 1ca7e9cd3dc..7e5664f80ea 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/shielded.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/shielded.rs @@ -20,7 +20,7 @@ use std::{ use zebra_chain::{ block::Height, orchard::{self}, - orchard_zsa::{AssetBase, AssetState}, + orchard_zsa::{AssetBase, AssetState, IssuedAssetsChange}, parallel::tree::NoteCommitmentTrees, sapling, sprout, subtree::{NoteCommitmentSubtreeData, NoteCommitmentSubtreeIndex}, @@ -34,7 +34,7 @@ use crate::{ disk_format::RawBytes, zebra_db::ZebraDb, }, - BoxError, IssuedAssetsOrChange, TypedColumnFamily, + BoxError, TypedColumnFamily, }; // Doc-only items @@ -470,7 +470,7 @@ impl DiskWriteBatch { self.prepare_nullifier_batch(&zebra_db.db, transaction)?; } - self.prepare_issued_assets_batch(zebra_db, &finalized.issued_assets)?; + self.prepare_issued_assets_batch(zebra_db, finalized)?; Ok(()) } @@ -515,18 +515,23 @@ impl DiskWriteBatch { pub fn prepare_issued_assets_batch( &mut self, zebra_db: &ZebraDb, - issued_assets_or_changes: &IssuedAssetsOrChange, + finalized: &FinalizedBlock, ) -> Result<(), BoxError> { let mut batch = zebra_db.issued_assets_cf().with_batch_for_writing(self); - let updated_issued_assets = match issued_assets_or_changes.clone() { - IssuedAssetsOrChange::Updated(issued_assets) => issued_assets, - IssuedAssetsOrChange::Change(issued_assets_change) => issued_assets_change - .apply_with(|asset_base| zebra_db.issued_asset(&asset_base).unwrap_or_default()), - }; - - for (asset_base, updated_issued_asset_state) in updated_issued_assets { - batch = batch.zs_insert(&asset_base, &updated_issued_asset_state); + let updated_issued_assets = + if let Some(updated_issued_assets) = finalized.issued_assets.as_ref() { + updated_issued_assets + } else { + &IssuedAssetsChange::from( + IssuedAssetsChange::from_transactions(&finalized.block.transactions) + .ok_or(BoxError::from("invalid issued assets changes"))?, + ) + .apply_with(|asset_base| zebra_db.issued_asset(&asset_base).unwrap_or_default()) + }; + + for (asset_base, updated_issued_asset_state) in updated_issued_assets.iter() { + batch = batch.zs_insert(asset_base, updated_issued_asset_state); } Ok(()) From 727e3e3f49f408b0d394cb90b92739c36c2432dd Mon Sep 17 00:00:00 2001 From: Dmitry Demin Date: Thu, 5 Dec 2024 10:18:40 +0100 Subject: [PATCH 28/35] Temporarily disable specific Clippy checks for Rust 1.83.0 compatibility --- .github/workflows/ci-basic.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-basic.yml b/.github/workflows/ci-basic.yml index e52c70103b6..792a60aa80f 100644 --- a/.github/workflows/ci-basic.yml +++ b/.github/workflows/ci-basic.yml @@ -35,4 +35,16 @@ jobs: - name: Run format check run: cargo fmt -- --check - name: Run clippy - run: cargo clippy --workspace --all-features --all-targets -- -D warnings + # FIXME: Temporarily disable specific Clippy checks to allow CI to pass while addressing existing issues. + # This may be related to stricter Clippy rules introduced in Rust 1.83.0. + # Once the Clippy warnings/errors are resolved, revert to the original Clippy command below. + # Original Clippy command: + # run: cargo clippy --workspace --all-features --all-targets -- -D warnings + run: | + cargo clippy --workspace --all-features --all-targets -- -D warnings \ + -A clippy::unnecessary_lazy_evaluations \ + -A elided-named-lifetimes \ + -A clippy::needless_lifetimes \ + -A missing-docs \ + -A non_local_definitions \ + -A clippy::needless_return From 9a8c0322ba8bbbcf05170dcdb51c54ebc1408707 Mon Sep 17 00:00:00 2001 From: Dmitry Demin Date: Thu, 5 Dec 2024 12:04:01 +0100 Subject: [PATCH 29/35] Disable clippy warning about doc comment for empty line --- zebra-chain/src/transaction/serialize.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zebra-chain/src/transaction/serialize.rs b/zebra-chain/src/transaction/serialize.rs index b3ed12ebd7d..35f297dcae6 100644 --- a/zebra-chain/src/transaction/serialize.rs +++ b/zebra-chain/src/transaction/serialize.rs @@ -1105,7 +1105,8 @@ pub const MIN_TRANSPARENT_TX_V5_SIZE: u64 = MIN_TRANSPARENT_TX_SIZE + 4 + 4; /// The minimum transaction size for v6 transactions. /// -/// FIXME: uncomment this and specify a proper value and description. +#[allow(clippy::empty_line_after_doc_comments)] +/// FIXME: remove "clippy" line above, uncomment line below and specify a proper value and description. //pub const MIN_TRANSPARENT_TX_V6_SIZE: u64 = MIN_TRANSPARENT_TX_V5_SIZE; /// No valid Zcash message contains more transactions than can fit in a single block From fb512d9b384ec4a9ebfaea099d17da85e13396fd Mon Sep 17 00:00:00 2001 From: Dmitry Demin Date: Fri, 6 Dec 2024 11:46:49 +0100 Subject: [PATCH 30/35] Update Orchard ZSA consensus tests to calculate and check asset supply --- zebra-consensus/src/zsa/tests.rs | 148 ++++++++++++++++++++++++++++--- 1 file changed, 136 insertions(+), 12 deletions(-) diff --git a/zebra-consensus/src/zsa/tests.rs b/zebra-consensus/src/zsa/tests.rs index dbea27190bb..9503025586b 100644 --- a/zebra-consensus/src/zsa/tests.rs +++ b/zebra-consensus/src/zsa/tests.rs @@ -3,13 +3,25 @@ use std::sync::Arc; use color_eyre::eyre::Report; +use tower::ServiceExt; + +use orchard::{ + issuance::Error as IssuanceError, + issuance::IssueAction, + note::AssetBase, + supply_info::{AssetSupply, SupplyInfo}, + value::ValueSum, +}; use zebra_chain::{ block::{genesis::regtest_genesis_block, Block, Hash}, + orchard_zsa::{AssetState, BurnItem}, parameters::Network, serialization::ZcashDeserialize, }; +use zebra_state::{ReadRequest, ReadResponse, ReadStateService}; + use zebra_test::{ transcript::{ExpectedTranscriptError, Transcript}, vectors::ZSA_WORKFLOW_BLOCKS, @@ -17,9 +29,70 @@ use zebra_test::{ use crate::{block::Request, Config}; -fn create_transcript_data() -> impl Iterator)> -{ - let workflow_blocks = ZSA_WORKFLOW_BLOCKS.iter().map(|block_bytes| { +type TranscriptItem = (Request, Result); + +/// Processes orchard burns, decreasing asset supply. +fn process_burns<'a, I: Iterator>( + supply_info: &mut SupplyInfo, + burns: I, +) -> Result<(), IssuanceError> { + for burn in burns { + // Burns reduce supply, so negate the amount. + let amount = (-ValueSum::from(burn.amount())).ok_or(IssuanceError::ValueSumOverflow)?; + + supply_info.add_supply( + burn.asset(), + AssetSupply { + amount, + is_finalized: false, + }, + )?; + } + + Ok(()) +} + +/// Processes orchard issue actions, increasing asset supply. +fn process_issue_actions<'a, I: Iterator>( + supply_info: &mut SupplyInfo, + issue_actions: I, +) -> Result<(), IssuanceError> { + for action in issue_actions { + let is_finalized = action.is_finalized(); + + for note in action.notes() { + supply_info.add_supply( + note.asset(), + AssetSupply { + amount: note.value().into(), + is_finalized, + }, + )?; + } + } + + Ok(()) +} + +/// Calculates supply info for all assets in the given blocks. +fn calc_asset_supply_info<'a, I: IntoIterator>( + blocks: I, +) -> Result { + blocks + .into_iter() + .flat_map(|(Request::Commit(block), _)| &block.transactions) + .try_fold(SupplyInfo::new(), |mut supply_info, tx| { + process_burns(&mut supply_info, tx.orchard_burns().iter())?; + process_issue_actions(&mut supply_info, tx.orchard_issue_actions())?; + Ok(supply_info) + }) +} + +/// Creates transcript data from predefined workflow blocks. +fn create_transcript_data<'a, I: IntoIterator>>( + serialized_blocks: I, +) -> impl Iterator + use<'a, I> { + let workflow_blocks = serialized_blocks.into_iter().map(|block_bytes| { Arc::new(Block::zcash_deserialize(&block_bytes[..]).expect("block should deserialize")) }); @@ -28,22 +101,73 @@ fn create_transcript_data() -> impl Iterator Option { + let request = ReadRequest::AssetState { + asset_base, + include_non_finalized: true, + }; + + match read_state_service.clone().oneshot(request).await { + Ok(ReadResponse::AssetState(asset_state)) => asset_state, + _ => unreachable!("The state service returned an unexpected response."), + } +} + #[tokio::test(flavor = "multi_thread")] async fn check_zsa_workflow() -> Result<(), Report> { let _init_guard = zebra_test::init(); let network = Network::new_regtest(Some(1), Some(1), Some(1)); - let state_service = zebra_state::init_test(&network); + let (state_service, read_state_service, _, _) = zebra_state::init_test_services(&network); + + let (block_verifier_router, _tx_verifier, _groth16_download_handle, _max_checkpoint_height) = + crate::router::init(Config::default(), &network, state_service.clone()).await; - let ( - block_verifier_router, - _transaction_verifier, - _groth16_download_handle, - _max_checkpoint_height, - ) = crate::router::init(Config::default(), &network, state_service.clone()).await; + let transcript_data = create_transcript_data(ZSA_WORKFLOW_BLOCKS.iter()).collect::>(); - Transcript::from(create_transcript_data()) + let asset_supply_info = + calc_asset_supply_info(&transcript_data).expect("should calculate asset_supply_info"); + + // Before applying the blocks, ensure that none of the assets exist in the state. + for (&asset_base, _asset_supply) in &asset_supply_info.assets { + assert!( + request_asset_state(&read_state_service, asset_base) + .await + .is_none(), + "State should initially have no info about this asset." + ); + } + + // Verify all blocks in the transcript against the consensus and the state. + Transcript::from(transcript_data) .check(block_verifier_router.clone()) - .await + .await?; + + // After processing the transcript blocks, verify that the state matches the expected supply info. + for (&asset_base, asset_supply) in &asset_supply_info.assets { + let asset_state = request_asset_state(&read_state_service, asset_base) + .await + .expect("State should contain this asset now."); + + assert_eq!( + asset_state.is_finalized, asset_supply.is_finalized, + "Finalized state does not match for asset {:?}.", + asset_base + ); + + assert_eq!( + asset_state.total_supply, + u64::try_from(i128::from(asset_supply.amount)) + .expect("asset supply amount should be within u64 range"), + "Total supply mismatch for asset {:?}.", + asset_base + ); + } + + Ok(()) } From 977af4227fbdd5e7e870ec9c5672f37a199f83bf Mon Sep 17 00:00:00 2001 From: Dmitry Demin Date: Fri, 6 Dec 2024 11:55:00 +0100 Subject: [PATCH 31/35] Rename ZSA workflow tests (including file, constant and variable names) to Orchard ZSA --- zebra-consensus/src/lib.rs | 2 +- zebra-consensus/src/{zsa.rs => orchard_zsa.rs} | 0 zebra-consensus/src/{zsa => orchard_zsa}/tests.rs | 5 +++-- zebra-test/src/vectors.rs | 4 ++-- zebra-test/src/vectors/{zsa.rs => orchard_zsa.rs} | 4 ++-- 5 files changed, 8 insertions(+), 7 deletions(-) rename zebra-consensus/src/{zsa.rs => orchard_zsa.rs} (100%) rename zebra-consensus/src/{zsa => orchard_zsa}/tests.rs (97%) rename zebra-test/src/vectors/{zsa.rs => orchard_zsa.rs} (99%) diff --git a/zebra-consensus/src/lib.rs b/zebra-consensus/src/lib.rs index c61a1fe408d..d2e0eb46357 100644 --- a/zebra-consensus/src/lib.rs +++ b/zebra-consensus/src/lib.rs @@ -66,4 +66,4 @@ pub use router::RouterError; /// A boxed [`std::error::Error`]. pub type BoxError = Box; -mod zsa; +mod orchard_zsa; diff --git a/zebra-consensus/src/zsa.rs b/zebra-consensus/src/orchard_zsa.rs similarity index 100% rename from zebra-consensus/src/zsa.rs rename to zebra-consensus/src/orchard_zsa.rs diff --git a/zebra-consensus/src/zsa/tests.rs b/zebra-consensus/src/orchard_zsa/tests.rs similarity index 97% rename from zebra-consensus/src/zsa/tests.rs rename to zebra-consensus/src/orchard_zsa/tests.rs index 9503025586b..cda60d24018 100644 --- a/zebra-consensus/src/zsa/tests.rs +++ b/zebra-consensus/src/orchard_zsa/tests.rs @@ -24,7 +24,7 @@ use zebra_state::{ReadRequest, ReadResponse, ReadStateService}; use zebra_test::{ transcript::{ExpectedTranscriptError, Transcript}, - vectors::ZSA_WORKFLOW_BLOCKS, + vectors::ORCHARD_ZSA_WORKFLOW_BLOCKS, }; use crate::{block::Request, Config}; @@ -128,7 +128,8 @@ async fn check_zsa_workflow() -> Result<(), Report> { let (block_verifier_router, _tx_verifier, _groth16_download_handle, _max_checkpoint_height) = crate::router::init(Config::default(), &network, state_service.clone()).await; - let transcript_data = create_transcript_data(ZSA_WORKFLOW_BLOCKS.iter()).collect::>(); + let transcript_data = + create_transcript_data(ORCHARD_ZSA_WORKFLOW_BLOCKS.iter()).collect::>(); let asset_supply_info = calc_asset_supply_info(&transcript_data).expect("should calculate asset_supply_info"); diff --git a/zebra-test/src/vectors.rs b/zebra-test/src/vectors.rs index 7937b19ba7f..90694653899 100644 --- a/zebra-test/src/vectors.rs +++ b/zebra-test/src/vectors.rs @@ -6,12 +6,12 @@ use lazy_static::lazy_static; mod block; mod orchard_note_encryption; mod orchard_shielded_data; -mod zsa; +mod orchard_zsa; pub use block::*; pub use orchard_note_encryption::*; pub use orchard_shielded_data::*; -pub use zsa::*; +pub use orchard_zsa::*; /// A testnet transaction test vector /// diff --git a/zebra-test/src/vectors/zsa.rs b/zebra-test/src/vectors/orchard_zsa.rs similarity index 99% rename from zebra-test/src/vectors/zsa.rs rename to zebra-test/src/vectors/orchard_zsa.rs index f4caefb4afd..7a954c0529d 100644 --- a/zebra-test/src/vectors/zsa.rs +++ b/zebra-test/src/vectors/orchard_zsa.rs @@ -1,4 +1,4 @@ -//! ZSA test vectors +//! Orchard ZSA test vectors #![allow(missing_docs)] @@ -6,7 +6,7 @@ use hex::FromHex; use lazy_static::lazy_static; lazy_static! { -pub static ref ZSA_WORKFLOW_BLOCKS: [Vec; 3] = +pub static ref ORCHARD_ZSA_WORKFLOW_BLOCKS: [Vec; 3] = [ "0400000027e30134d620e9fe61f719938320bab63e7e72c91b5e23025676f90ed8119f02c71c7ffa660028b5f3bc0b0bedf9b76a829ce8f2ef82c2c69ab6948bc9fd00a80000000000000000000000000000000000000000000000000000000000000000f2fa494d3fa60c200202020202020202020202020202020202020202020202020202020202020202fd4005000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff025100ffffffff0140be4025000000001976a91475dd6d7f4bef95aa1ff1a711e5bfd853b4c6aaf888ac0000000001000000000000000000000000000006000080f8694a1277777777000000001c1d1c000000000002000adfbfe7961473dc7f8ffd411b3e2eeb005a37342e6081d5121f18f5648c8480adb28949796e09a38118152905839afc125618be1fdaf921d188488b607f2544e12a249ab310f17a9349bfe463c7de09d2b822ab0efa88b6d32f77d7c38793192b944aeec0ca94918390dbe44c50e706407692e348ed9b7cedd231941a673722ef1e7e74888672b2b2d08c97a9ac114b7039feffbeb8bbe197db4a0bca8d395cd40551c1d5d788acc2ad09eddda73a5948de2d9e2d82aa638dad6f5dc61042d6850b926d944f29f17e96eca84684252c97ce4382f2642e54208929a4b37954e8e386c677f2aee3e8f4f4aee9f76a87d868fa2210445c09b927b842485918c869a23be8213ae21937a8ca83406fab193cecfd3fcf3b1c698e8057a6c87c059dc6f4ccb30af8e608a7c04088cf3ca32ab20cd780da9443606b092c8b5d85c9a76433c0993e3eee385884ce1f3890abf95462c49bed01a3a5c09df98cb7082e9770bdee196f8b968003f5cc76d82bf575f01da3ed40e44b3b15721f4a9dd5ecd14fb71a42b24ccb7d7e6a3bc10b53ebcb7e0ec6ace91dbc19801eff0c76ec0c10602bca2cfce9f3e79536a25143d351ed2894b4eb4e549960f212f0787057ab1ac8b249c3d3ff8652cb3fb17d7656d50c5e6833b056feb26855332f60e7b8d1ebba32df63d8561fd7d209a1e5adb9853bb5b5d6a41bf1ec52d348023e945bc02e8d6ae8d5b6c7a9225991cca4aa0b41861f237b3bf545220799f152767c7fdcc693a989057e119c18a96007c69c8fe5751a4258ec3f0f99c1aea8dbbaf4df9953ccf6b42cf2f4265011ca89ec7b9ef2c6e9410886291054f50db6310b225ddf32f9db26416da6ca9ef6e3198db36ebab9802517aeaf628d41358fd141dda8fab32fcded20707abc3d00191e0b2690a1e2fa044814191323155fb21e3da8798e0bafef8c2da4c73f967504f51e716cf87117f90fd028df17f3ea26aebeda7f5b3192cd5f4855044e9a41bbfb817074ca1680a458338b191a9619dd337bd0335cb1896d79c79cce10e454b58fecb1cf10da9f53129bbf3cae2bde82007ed98505f16922b6ae53a3a709c2e01ff7e529925d6069807c06bb0c73abf8d463b2a944a97d150935cc76e1ae1f8f95159a928a5afbf76d54544a771fd4bc482ca522274b94c87b4f1c7cc3399709b5572c5133bc945cca63bad59b454ee301e3582f09c5c31f326a59705a2b534d8e9a79835bf767ea563b0aa74d3301c40a303f6fc04ba0f3807c5decb6743aabfed1a092f88975820c324e2229829462e4985e299c2415eccbdbb4ff26789b74e91db286e6a4af023e8a18e826e930d9d4ac8d92cd8a1098d0705852cda367ba067e723ace9ea8b9502e20e6519dc72b1cb477c4f3091ae4d20eb7401acac77d923eaf5de00ecbb61baa3aca9044f3e66262245aa9f3dce1d02a88e8c26b34e3c27b4e4e5f91cb633c9b6e098063d052dd6883d4c2b153c739ef78c5f375c640ff747adc1110de2f9d011118f3208bee2f3af9990d56ecddab1cfde0c053020b1116afdec7a3303fffe6f6880072482f95aa3115724814aa5fad017e3b7637f3dba509f1e371c9b87a275cfceb68aa5317dbac0e1959367d124935c76631b8aeb532d99c393374f214af2d6a3a5bf4071d97b6ad39b5b2ec03f1feb520ce467808eb2cedb3ec933c20322bcd4511b838de111f9faafb5d45ffd8edbb1fe8f0928d535ab9809b4cbf588af635419b10f7ad9f4418c766d88526215b74518cb6554e833ada2dea5e57776a09541d76ba545f8a727bbe7722912cf00da4a48a462a5b7b13c88941762462142f97e8da2b358435c9cb53d24b6443ea2e1bdaaf6ce58dbd0bcc598cf170a193e14e76ca8bde66ccc786bd330c6ce61db5f202b01c7faf185877e3614c1a1b4484cae6dbef080142f8c45e3e48485746fd3505bba099ae7b37b96e22b2cfe6a0dea5b017974126259d5055a28ad510b3b7116c27287fb7e635f1918d5a9ca2529b1741c9e86c59ddf11c3f70a56fac7c9607eb9bb36612494ed1ae819c092cfff73b7c9c5d3e8680dbe73f92b749c84363c374d80632fc488d0b7d35f25ecac1c151ad8427d7a4eacf24fa6937fd5c416776654bcfae92d999b51c49d76bd53a9d5600b40915acab5d31f0ea3f7a68adccbb72cb454164beb35819af0e9e06ecb40e96c9c2aa8018883301f65cfbaa7ea894737d49b44aa5d76c4b26bbb6de7126bf785fc2a8760ce1664150be0b6828659513561b52906e6a4782732749897a41ffce670736ce0baf5730fce9bcb50a44e1e9bba166f4812ecfdbc2ddd8483405cd2bc68ac179177e1713220348da35c7b2a30c9ad9670d99a53a3c4c4a611fcefe9e39024732d6996568f2fd8eca433a41664b070000000000000000ae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82ffde01c599a33ae69b9dcc093a546efd4cdc2c8daa0479ccdc63123cbd0622fa54f8c15ce9a049727b659c998b2fc935ddfae5788c51772e00dd8fafb91e7b9e7b9d4a34efad77ea3f54eaf8bb5bfd4c5d5ec6761689042ec6b1639e79d2628e712669e32f33d058707141549b0a0fc31d9fe4a633871d1ca48096cd8272f735a0838bc1a440947547ce52183863bf080eba73bb36f5130d7dc8676be2e28d00714dc36ccc580d88f6d878357e7121a811a03eb12faddcb75c9c3703ccc4afdaa85101244e619f565a5635e6b8c856fda2edfc27b5c06a711730beb1c361a6a916fd713ba64385734b8563775d66adace055205c6cf9a6c90faca0629e7b93511d0e51e3405210bc3d3c590ead6671e57af44a9418a5d3c6369d5b6d294032f1592c601c2782f5e5fb7ef820f548a7e21661944982f5b04f8722ebf42456df6748a2f9ba2b816bfdfe1432f6c4911daec2b75802d43000403272e1de73bfd625b9742b8970133c0599a17cb7fd7984d6a3da82e845e179ea888019c6d86016cbef610a7a0e3409f0a2bac1181ce62a22fe3fdad2708225ec503077caf354dc5c12f6fad975509172383e2f87405fc7c387b1de333f435426fa3b8a524cea377f3c24690918a4ea2dbf4940ded498169b9b85adcd9d37175ac43897abea5d629775f4f9792d2ece6ff69dec38e38d0c1c9e40dd2967aa103a20a148290a9b89ea82e1bb5235bfd29d260862365933e19f81eb19be2c775707433d66c15f68e5bb8a578a925f20e9d1bc34132c5a214ade50ff48489b89cb674fd3a9c787d0ab539849aa19486e3d4081d4f361517f45fa35168e0432fbb69251a6a7e8f5d33b30564338f693e636d04203502588b4e9128744f49005a77e5de0f79e06053c01e82f4bc29f0bdaf3292c300030eb758fe2a7e98f41f0db618ecb99924e25084b0e69da78bb4918b365b8c613ff5e033d4994e176b5abe710fa552b3e5e21f59a33e4e0aad74c0504c2eeffcf213301b35d9b0bd3c7d140c849012b1fa7ee177e994366b9b278afd94f6bf9a65bbd1cfcf5f3525512e5b257f6a5cd61c43ff2c695cd9571d8d0e24bff92a5ace203d9a643b7c52d794b3a6a2f0cdc6c8c1527e51b32847935dd0c12b1f1aef49cc40d4318b5b067ab9d238e7dc4a8903d8ed224c15ed66b11043fb6ca6109587b6210027df615ef57125d696d0de758be6e4b1693e260589e441ebb020177a4bc7c577a7f2c7d415e00fe93cf1436bf13f738f0cb7d0448074f1436457dfdc03217b585d133dd44928779072129072c0cf0ad9ff3fdbc686f12d219313ceb77e01846f030d631c8014987081a1e3659239e009105143ff3fd3d999fb10e1a1b8f0adce31db881d5da746138462e5a1b45d47862fff760d3b1ae1de946257f2f0edd1bb911495fe1a24ec3a7cd5285e5bb25ac1d206d2f926f9dfd574758a6eb2ee5faed26e5a8e07dbeb11ba4dcfad69d93cc718fca7658b97384a243589699547d476887de967e325ba5e3b982b079eddf83998849579a849dbd3f2f487eaf9242512899756352f3680f334e0585bd43a3439bd62404297912e545e7c18e0c2e19714752b7525bebea1d83222648bc9457ff3fed4c91b1fabc6c88b5c3805dde0267f72a0abba1715fcbacce4253881625026ec2e240e8bb98c5316d28d361a91a1a572caa057492fec8d5e8d8b51f5890515186f7c97ba4a3810f40e9916567ea7a1980fd806d295c73a8b1243b538c373f531994507ae50889cddbe473a8dac128c98eeaa965cb0cdec2dce36fae175334b1dccd48839b7d28292a7c753cf23990d111e518a4260631d5a99a28f3bfd01db75d8271d08556abef553106fdc472ae97b5f4d2d3741a5104d06560d85f48d3e3acb040c3264d37f370f2acbff61ed733f655d8815983c7e78942131b41645f1ddcb24711ef39dede1317c4d7dd9b01f77066b9f3714b5f09dbb35e8341188e4384bad7238d9ef7d73e2055bb9e6706a90348ae9e5057780666f3642d7a18085e115e5ea3447fdd013a7d976d00e39edee09b271286c1a325161d3d6eac566cb86c5af3d287b5a56dcc48faec1d8342bf3c5436ccf03c0b47cb880aa7001e2c5464a406e24dfd9d021e62558e3cc9ae3228f03ddab021d5519fb426551e0a39ad08f68229662f16b79b7653b8f827a8527f1dc556a02f9e3c7d0f3872467a60340dbb0641d91d6956a33f5c905069ac39e67b40fc8d5dc617e00e89dd926bac628eb187ca1d0c41972b73b628f18159633c6d4697893bab32cb760a193b57034804a66381e62bebd6b729294bb14a113c5750a2bbdb57d40ec9e37ce7f3a486b920bf2972779b88d4ffd3136e2c10286682a4c413ed2991ce333060be5e348dde9eaded10e4cfc84ab1b157713936149239477a0b5a437574a45f24843ec4a525a71813c05c2524b92c893cb6aa0dda8df21d550371f5ac622038baef7071007a31cc486158ca1a8ec2a0c274ea26100fcfa5a3991b6f79384ae487975207d2b0b068df60c7bd63c014d13d2b5215ba7be1802b78742cee248c07cb00f3d5472dde9f85a1a9a323125a3cca08fe5c8d89f73c3fb600a4a3c7f28f12ccdac1e911c8d62242deb1ceb63eb40ffad0ae8845cc9efe69e9f5a7d2cb9910306e16b529d8e16e235e8e54eb84859d4346bfbfbefd453f9a4c8f4cc5c6c80a616433dbbcbb512651578d1d2736f513afa0fc68b401b53086d4a32d2a73100b59f8c07c06f43d17d2021fdc15410e22b5911b3572d5f7996703e97d24699da3fe7714ce74a1daa802609cc631db787abb71504d8c016cb7f5973c0d5f91899bbb100b97d4063ca590d15f176612d2e8779f89132428c6a17ce0dcab8ca081b9d891d3d0cd0bc755a193c5d5180d28d917ea7c5121c702e7c66a58b5499ba4fae3336a2040c986afd2f44d92047b338db4b6b3b6c176d88c641a6d9b4d4749654d55785002b3201ba3eb86562adf07f94b3e39bb3304a2d022a872ae74cbf27f0194c5c73037bca2d3daf1150aee2f81991aecca23660de5072568652037ce13944ec9d75f7cf424607e36233008df0a9707913985b837c631288ac62c253c9cc1586706b9e8238bb0d3d14fcfad900dff65772b36ca252d9f81450ec29d6be025262bc1104a1643c099b3bae8914c8c78cf39d09907d725a52f3ef3a9981c2dec6cccb88017805cf2163e8909eb0822c34d2b42ed08af78dbae9484e7e4faaf2a40b0762f23b491a2ccbdb5cdb4df184b2b70cd39b0fd39b8a50e4cc527f4c6169e79e9c1cca54900a1624e198a0214d8013c017a2dad0aea1521269505213c1c873cb5531b6dcaf1c5430d741514e49e7f3c0f7bea8c9e3ecddacc99e2a8e729f8a0e9c87687f11158aaa9a7159ca567598add54fdb1a58eb08da87154c59bb9214e9d63fc280ce2fe1300b12d4b805d2d992a5e5f74b04e6b41ef9e4f364aaf3f90aad6435d7662d5639882f9edf5dc7ae1e5623fb1cfd9578fbe00cf82353ecf865d9ea24b5d5050e6f7609205b2ef209c57df854ab27f2dbf047e69666ecb731f0b11e540edc105301dd9b915fb4fb1d96f4f8b99b9c42f55f99cedb22638167927766642f0c1f6c4038d4ebc8dfacf6a3ea59532d6275fa5947cc80f44650719be2802f83f62b86776c7a8ac0b92305c69583eb7b1457e21760890e8b9f42f0043af46d07f82f8aab3168ea992bb165dc7396aef85646148b9e9fa88735bcc4f2f94d70fe02200480795aed487d24810b4875284d8e51e25493075e17b7f9f319da50e339a61412cee460382cef9feefa131bb3038360535c5593039fe5fa3795bdff94b1d41e0538536a9e6de8e4a9228d65bdc5cf6868680f452599112cbb3750f9f167ed33017d61dc6b6b374d87384d3a81e74289bd5253ebd20edd58d54bd3711fed8b2273d5c39ab91cfa21b2d3a901891eff40eefd70b8d0d55c1c33a9bbbf2e0dfa2430c736a18addf449dbaa6ed37f04b5a921f945bca6bda7cc75fe47f4c8395918236dbd810406e684aec3eca46c8079dc76defdd90c746859df26c661e746260ec99f15b3bcef2d4eba263d6563f305d522b58f2a39d9f420625b2da43f7dab24c63ac0cd79078a56156ddb4a295057c02dfd02bb52511d08547ec1c0be7a7a1ffdeb550551cf0170e89d9ccf024e862eb9df3458bede7e0bc7060860bcefe43e526edc7ed295f331d5167705f7f32da9721abf972e7eb1235344776ac19bc23e6b916d3a5e54f6863dfe0f46b17800ca77c07f80f0fbe0b2a39fdd2e0107e53148182c577a60a52ce377947c1c44f9264db8fe29b5d9943ec70997fbd1759539f1c5c279f645b68a856d58571bd99d0589f444f239f194c9e73e1606f8affd027a78fec78b8ce11a3871e416307c4357e761b6836be85570d3f155e9d19db103d148cf9dd8b51faabd6157e5e80c9b78e19501489fb6fabc2c1b7de2d9f480006f0b5858eae39893f9ec8a36ed92f2d6e64a31a7c1b13dfa8540d3176e2d451b09237feca9752c8e14b48eee5dec0cc314a00cf41303c8af57c727140be157376f5182e5bd20ef43bfb73077f388b2152c79b40c7bd7360aa0da790677535a1e1ea76528a51b5ea8ceecf9babab979606945dc154ab3269d729996e6f7ed843e8207cd7893e5f8be32fecbcae63474a8f3d3e66f5ad3be91ebd42319d4d4e81377d3531f4bbb7279ba63403c9d827875d7c244a9e7a7c83818af42fee45603039becb40982e1ec43e71c919a409cebd605b865e99936dac09953b4be63ee592eb0f1bc6c8a0fb156bde6c4e05df97253dfa07ad950253f18e0bde6eb9baaad215c785a73750c6f30b36acff3e760abd513258e60d80770b4116cc7f925f34b286649676697b49fecdc8e99c6fe3311d34fcc8c4cc1f066ce680bbf9c9fc32722c858204e9f8201dab9bd6639830830e9a24830a2dfc02f767eea40019df8d41f2e0f63562cdbe55f71b136d52a61b271a24e5992a123f08babf356fd83468d20ecb634bb0ad02be4af5fa5163445cf5ae233804ea209f5c279c726db78f1c81974fb8cabc783e54ee537c9bc3c83370bba589e1389bb1e63ecf59250dcc2752fe0e1081cffb2e7f4c62d44e54a46480a809d383e81106a1b06165f419a8f3502cc7fef7c9067599af2f049fac6ee80b15122555362f7419ab7f3379cf9f27503c503eacc8e94bde23efcd0257fca4da1ac39ad5f580174c42860c91be20a8b1b95c2ccd2a51466da013a02d728d54c168eb50c064b30da49f272f08ca19099058805f91afce70776194f24a151fe36c2619df9fab6760554cbb58781514f131f1ec127a06d98e5ce4ba82fcf1165937ed258ddc9ace565827b6b8cc009b87b083119fc093a106d5f5c679e7a145b619e34f69ac0531a9d7e17ece8e335b66f14fa874dafa045603e127954d89dfcc0994581a48f54fec32d4228831dabe01d0d9f887f4604e975326e8cda35e2151a452be21a4f7117740e70bfb98cbbdaba32795fae8150ab9be24746faf5c8a9ab253cf34f2807e30a238a3ce2aa5de2691371c49b1475e62444947f632da3da60786d0f1f52a8ddb0f69bb293540830f10cf70b3d84609d16fb1c6285a4ca9ab615ea8b0aa6274317dc9c06fba50e001d00fb9db760fe6e4e751a720bb33cfa914fd5ccd5a5e5ee325805cabfbdbfdbe82a45aa53570a50fc22573e6bbf7fb641f16d01f44b9176f965b1ae610b0bf2a73fa1125b14bded8008d3d1617951e19f225d0698241746b651e003fafa16764506610fd92caf131e8c278fece483410fc3e2c6f2cc76d4a9b66028ff3aa83d4a074cce66ef035e8f2186d2ff9ed2615b0c451c8564b812f225feecf9cbbb2de6238dc4c8770a0e17feb7cb02217da98318414257c4dbf3022d8f1e5ad79fea78168c8f1771affdf4597994697e6cece2e6bfc7219d3018e3ac49549e37b8a57e0e69ef51c8944a3ed215f36c15a2883aceab247dac03ef5a82f235fea559e6b42cccd5eafc30066a3a3173bcf2f7ad34004071bd080e69c7ad3f514c62c928e63457afd2973142069ea68111c6820af10202db0396474cb2a78e1a7121ec04900d9f4ebbadf3d306273afaeaaaaf0d882dbd511146f009b748c2e093f02baac204a3b4ebd4bee5aedc3935775b9d01cab2723ce0c06ccfd5e2a8a2c8fc467c9a06ff3964e96e104890097d00a99811114179536be5457dc37864f4b5f848d27d28a6143b90bc2ae09b218e867fbd6791404ccb662fb779119b8cd2472d1f9e360ccc37f39f2019c79f365c813fd80faf189985f1704016f096acfc6bc0b674fb117ba7eab0f4138791416638ba365c546180b8d5662bfe157f3f63430198548216d7cec0ec8724ebde55883b2c384cbb67b2d7179362f9114dbbe561c8acb3d40ccde56ea66cd7c832b299a96f3a0e0aebb57e9246068d5fbdb126e6a149f7ef2214c35f30409f1b44de792cd741df0cf48f273f6dcafd69547fde219908a75b3b594f45a382ffda619f7e1378df37a8b2a25aad273329002ef931a95a0a7b670dba6dcfc08119783f60b84aba6ba878de6158e689ab051e5ed1743f6fe28a3c061198e7a49d08a68271205e4151c49264929d9ba38dcb2559f45658ce96b2c232cdf40e63bd8828794ef664543bc2f700a65d7c86f218a9b76ad0391906f4480f5563b434403e35eca1079d8c1f906a271ffa21d669a27883108ee78a4fceaa056c0bd5aa4496ec5f37b2dab8b19abc88c61ec5891759c6fb2263f534df3d7116d6874f42d8bc3a1ff9383a68ef3955295b5478c28308c79ab25ae9ca31544427a2cde901ea588ba872f37cedac395f6661ec659f1bcde925f6a82502b32fbb07d4356efda64e82c35356f9abaf5d3f0abbcbf0b0fcc2191501aeb7b59b21e00858b19492aaab25c62afc3b0cecd3d7746eb6cb1edb0cdc569602791a17911802c9f5ccca92717ac661cfd4d4dd8bcdec75492a64bdd2150c2235e7d87759e137b213cb3ab4e275a99e4ac77fda073e2870b6486ba384c44b4f59b382847a5d0a4f87198a996e639f51246014a0d9751db9f85bcbea056a7609332bb1e7ffee3baf262a346e45697d9c97c5ef099e109251368b5a807e6b69c1247e8430d2ac2261aab0ef2a0f695c22086b86fee0adb6bdc8a14af3d02ea0effac0f6f55e8203503b48deb8c8673b92c499284b935abd06352b391c253e35870f024bcbec4332f578a74d4ab0be09e73e3cbf5e1ed7e53eceeeff4ec26941dec578ce3a33f701bb540da65f810e7f4df368804cfcb6078c99d45e4f15ee1d1ad6831c3e6e01102e6ebeed1f86940de0759b256594c9f91041716bd57ea464e77cca292090f612bd7daf20e9b534bacf13ca7d940c90fa9c18b188fbcc17e282edb1156cf5c1351a9f118dcbc5cc720c5c6dad1ccfd04f1beab7817561e86442665b841c97150d10395fd842b54d025a221d81f05c820474e492341a0c6dff31f4a38ee089082f7bfb17b9d8c8355dc76bcefcf0c7692ece39649e85ddf7e395f1baf893eb960d8e1374e84a1d32fc1924ec5808c1255b34946db13ada6163b368754820d519197aceb746d33f556f9932aa775b5547d4b42ab6433e4adfea54bbd7d173e622229660e74ec486937fba081cbb26de3ce7f6f76e070cf54315f18b03675cee1c06fc765f145b7fe4fd12f897c83e21c299fb9614533e163564b9d3090cb00f253029a3a4042e2047cead0dcd42969685de183ded532773056cebbe0242e9bdbf079eb9c8b0a64df4833ad35fc40a317e99070683255e7087a0896b20e0b483410f9e4913bf36cd028555302162a6c6152803b31b8dede9717b80947efaa233f6324941e0714473c92da512fbec873e4b745505a5e691e2f1b6dc2e98d1cacf4c1a42dff6e360909bb82027b6ad070f34ea2d1bea39653da363b2dd14633f5d0f11cc1617ab8239e9832b162b2bc18d8703a39a21ac2ce9b1f23395225f6b34671d5e7679459e7f86391c80e2e3c350220f3f3cb40e575fd8afae3bbe1104246b092e405bb740e213734a5a171aeb6d82b185973a797cf3f17d77cee462e4f3032d053044aa6d8060928f6227bee4a2ad6f7cc6bf49df364cc75fdb9c9aefda07130967032ecb5a29b38bafd4e6755e2427585746460d696c9481db581ccd583311b68da1e80fc45b330e7fc6744105cf7f329effa8d05e5f04f891e6a45c0f620a1c516f22c796523a325d03aa141674b2d257074a20a7b14308310c73ccefa815f73715282cb467a763532504523a1b1fdcf2ed3af8381fa967e02294195a9d0eb43a1f5413be08d6e9ac6e95ebddb34f6962bb64dbde6e94bf734cca4cd1d70feb5b3525d1a4f8551facc79b5f00732cb252e9df686627a56b80b13fd033cf279cfc12ae321a0fa58da9df8da8e6f9f64214e40c22334f13bf1f6da122b4673deedbff3f98958b53af0f4b40158d79e63778123cc6dfd55f43f4bce42f318b0ac418dac56dd9436e78bb527c37dfc28180fed5c439f952931b29e271d83b633effe9809a6399282048357028ab1b540cc0510ee6b19a63643714857fe51c2b1b2963b7964cb602861e81eb52348453e9bc497c447e8a8eb73c79f3997ba17f35f32121ac7b0172845bd8caed56a79285e97d17aa467312c4d10b8bce1d18e416c383b128ded04fd1724a29cd8fe9377ead625ae91efff1a562e03d382e4b4621b28f717ac6fa928dac4a086aea4e122d59f28c961ace3dea0bfc79eb62a5702870bb86a8d82e6284b39f61d2c39b7d99eb65f319cce48af91a9028a48cae8c3c08134f7285c9e7161a570947fab3497f00476f9ede57415cf5889ec18501783af4c371a24560a3046a2683741e851ec1c34fe45c777ed5cb03dfb8ae6648a1224bfbc723f1a69a9edc5ef37147baa1a84b199be1dd645dcc0fba7ca9e8365309f3669b6d1d2e8a47e21d34d1405e6530e0d200dd9997ad72de1e70e660dcf53a6bc4bccd999214ef9206af79b44915e9956f8a019919290066728eb9ae5ccf073eefa4b9f771f584c03648cccfbc1823d118326d7488e2fdb2319df94a593ab0bb34c9970d038dddf2c174631f7b73eba3e6fdae9edee2ead25e57f4c498c32a567c546f089930cabc63db6421a25915714aeef8d9ccd320237cb0e4d302fe1c964c4aaa604714105a1228fe5ad6ca7f42fb2e07c7d6b0bae5f3b320f59e9821d0f66b702e0bef73c4f3d891454e90599f033a96da7df2faf22455f49e28b10ca126096573ceb1d4154791bd607ab67ddc372cdc3da2957e67ce2c599d50b90710895a934fe744c3cb75b1836eed5ac9a549c28930a6388a7c993c7d5a5aa302ee7bf08d177548ecd98c65152d6197286f52b57a3f918218fda1241e28c86201d6e3b6ca12d8e6756223bf9b19387c321db1a0ea2fdcb7a7705f7e8c81a998368a1cdb7788be5629a43704d8e91662b3e1a5ab205f85a27a139a5dd5e40cab92e6dcadb5be50ca3343905fd10ba97df8aa658634c914db6389809d9b18f59fbe371733e5ae1fb35f0f6230a2394119aca72cb11db8a0d0c82a0313562b97528fb50b99f21e3c4097366b763b0325a2f8875b32cd4beadb07925be74aa54aa89f9b52eb1394e1863899f04d7fb451fdb81fc4360a3320dc2a24b3b2c0fd463d9906b0797c3215595d59e5350da3a8cd519d51e76904a80d73a163b384fa68002516c7d7efb1f14aee9258b3aab9c5033b8d929430ef742cc88665799fb1207f2c8d333db1ac85d4c15235103d28b3769df98a763426546b21a8eb0f67872edc8c9d448b8c70d6f7af172d13c3aac5d4ae5bfc8ca9c891e501f2c473eac63cdc16a96b0f74cffb89211a411b0e6b4a0d794b5be83a7cdde651573a142789aaa6aaf76c7f6ba4851d1eedce7feb5f7a2c1179e351a6d97620395b96850238967e8264f581ba4ad4dc85933c874e30fa3adf74901f6ece0504879356a835eb019e12e5761f5555f63c91142c59cb32515de844c0284a31d5e148b694c53e3c69378a1c2880e893fce50f5ebb5b46b7ddc8753e7104f5effea9b0c36e3720469c3f20b8d97cd39c06cecf7881d20032be0f23ed939613cb0dc5ac81ece654aaf5ec36ba427cda4a0031328afc840ffda24b1829153682cbee0da142cfac74394c073def27b4b38f5cdc1c7b699d281d1fd41ac559410cba3330d16c74c8d035ef0210c8dd151a3850db594502d1d50c2959301c384da313611e361e71e937a5d1799d1a45398ce25b1111c86177152676d64393e6ed1f11821c1fb5dca4cddce3a3b1e28975d80dca762c79210222f6771d20ac64da695035d00dae321be393b17008e5f0037f4c1733e4a9f17ce275a85fb44ba59edf9e20403843b11863e4db333233314661bfaf853d6269b187bbb6c0eb0f510d4912645056813ad34cf3bfe5277c589a0314bae0aa802cff46b510c6c76938cb84e921f7b4cf4200da1a82942a807a2075c0f7dfebf768b54b2e308dc49488c4080d6c71c0bf8d773d5de3cd112c588a8ffe11d7a17534a6c7fef432c380ccf252a10d8cd1fa13a5cea6e546349923de83cceee44f2981984fef7144be4e72ea0c149458a7aa6648c9f658622c00be9d0074d3b0d498e475c8e4bfdc9ed4ce81d3aba532aabf7e18d5097ce37505c2dfe9df59a6bb30fe45d62c8a1f2b065ca8bf74f81bc3da5ea3bc5fe855ed0f0104574554480151238458b0d0dd6d493ec964a7462117237ea214ab8bb54b5a7d9e005f5606865f5c7c04adb7149725e02ae803000000000000c9cd432edea87319b8bdf5b400d17cb0d4743f2174c15037c7fd9e5cdce945862d09879b6ff8bdded4f70af68cd3e81dc71a4c671032da6cd9224a5c6c1a660aa1393872b9170453d05c1f40ee3bcb8f727b3e196cbb9c72e7f12ea97080f67e003c99764d0dda139b3165da5dc4bf9700c6a563fcd0543f549e7b19d4cc4caf777c3aac4386f3bb692fd45d7197df5894f1c9545709c9c2255a3b6ed950385ba5a7c9c5fe91bfc671695898f78518380e34231b3e36a49b641cb3e940beec0062", "040000007d24b5bfe1999a3b21189a3c4d4867784bc2105a0196aba2ba6fd1c9a63e22e1be3fd8ef559f3e7d94c5da9f3ffdb276804f413014d8bf07fe14907d6a37659320e538c79c0033eb2537c88a69d77f048cf4cc4fadd09c9bbb91b4d965ac8f2e0a104a4d0f0f0f200202020202020202020202020202020202020202020202020202020202020202fd4005000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff025200ffffffff0140be4025000000001976a91475dd6d7f4bef95aa1ff1a711e5bfd853b4c6aaf888ac0000000002000000000000000000000000000006000080f8694a1277777777000000001c1d1c000000000002ef753da29c8538cbe9669c722c10bec5663e07d101f0a6c3f1f86440a7b00dbe374e5118632c4075f9e84b6c62791de12f1ec0e70e7d415d61c6639d786b1a0c0289051e9e5ef26a5dcbceee48051ae1ee91d70e02022fcf954f3d1190186523ec4cb0ad65db85d28e247bc1daf3fa5b111983e5d328166df852374f3efa9430f7e7d5ef94c51c82437ac68d11f78c190ff7314fdfe4fe007c0be3aeae7bc1094c0be2db5c7d2b24faddb22ac70bfa8499783f312f0bec068f8c09483f7b7edb6e63753d60feb460e2ea1f683740eded3d994f602670d174d38dd95b2a151d0b1d5f2592bd522084eba11fe9f8fb1eac057b84bde9119816ff74790db723529e8713c8daf25996fad08f2a78ceff248ccc91a83402b94311946343866a6dace2226d246cb8226f2bf7555640d4891457a7f6bb6c85962a5e482ec760b6d6483a15f6b44108c2096492765fea12c37da638a7add8d0b74b1bbeb5784c3712349881b78d229a682f024cb21c0c3961704a71ebd54be06a17f44b1fb1926844f14c3a9eceb626fcacc77ddb138846bb40f28daaf7e431d5d09d6f2be928bd09b03f6ee2302cf572c781cda2167d7d8e9b1f4667c8b3f7621c0cf85aeb45462eebe743a33bebb34a9d118cb2d4a69d2038d591e3266e77e122f9fb889ab83325e5d2ab3bea0e85e10cfdd1508d3233ede0b9de84634972e6d3cbcf9325407c43fba5c9dd30a70aece3ac6ac3d5598fd2dd29907584b85398cf21879b4e9ca3c2066e65fad046e788e56fd4a9098b5a4b0fbbe12c0f7c0b5caffbbfa69e4289c9cca89ffb3158dabeb2952a6af2bac251010a3644c01918e0198b835da28e26f694ca21d897785240d0477cadd8bd03bad34639189525c02fea6172168722cf2ae9a6b51412b4f9b24495b9b2852cf045c1acc6d97dd0d6746dd116cf8bbce3258c862e1fb18a4e91d9118c5741a38d6a7aed613910b11cb881cb6d1437669ad853512778ced215ff5b460a47cbfd30e86f9eec227fc123262d73f45d71e66f17492af0457191e797ac9fe6149f4b3cd631dce8f9844bf16588f55003371165f0e48562a11799c33c5b4e4dd390b3943fddf0162c033f0751530acaf5ac2530f320c157c498452ac5012adbde2cc19339fe82e1ee6245dcea9f587a40f3b78600de1209ef9eb6a903d267a95742c856aab1829fc8974731e49b6e8f674eefa81b23026d0bcb1d770c31a60232798a8828fecba930b51b80ae6b98645be3c5b80b195828dfab3bf8763ae660dbe4f02ed5b52abb301b18f3ebf8f1f81b8feeeed620809673472aedf9d70ac86268b7a162d0f46c0ff6bf52bf5dd289a9f34c19632198cf15730427971369cebadb6e943a6d8dfa84f83f2f6451e9d155449f6ff1b41f538eb760edccc3697ef679a586c8295afff2cd8de2ddcaabecdd1c0b41e8db2790ca35e263372e2aadc2b579fcd47d74bcfa188dabae48a78eb8e32e403a3f4bbf86b8535c568a332e0b64de3b3ba0e75a2ce01deefb1b1faa6fc59cf602d1359180616258847d458d03990f158398018e63abf87086caacdeeabf6daf6965d184bf8dac33b1c5af2223168e023a2f5021874300012761f400e35e341e3b54683442a1bbf7907060b54181d27021ea69caefe8326414462f03c44eacac9f26c8a37a8eae78c76dbe19d33f6b198e8a2f4d77a2d50bdc9785518a1e210fea6451bc05e85bd106737ad37e9c96105db1b9bb09bd7cecc45960de0bd6d803913fa43935a8c17de7bf573089ec323b1aba8f6eaf0603b91e53c540305cefe8361dfe47b787257add20569bcb7aad355d93dfe9d28443da5662fd1030a8e251fef553877edd1e1559bba63ebcec258035548d037eb34276f4b256b22631489e8f7201c86537a53502b6f9b4ae7c2a7272459a4df0d203b7399ca1bae6b5260566332b955e342132535e527fa207f8ec9b0bcf9442b7794160497121720d2fd698e3eee28fa34de2321afe580958dd133b1dd2b36afa84dbd004c4a571afa48466d3b7915c84753186b5a3e7b724a8fb8e411f8732b963fe81bbaa48b247330eec8a0897ebd64a25032e8aa4c987ff8153bb447308bb3cf5ab699504746794711928457df6e10d689d81cd6a846123375f5c46ed603f14b0ac6c9729075873179c3bae9740c273d0ec9e1ce060285211c4e60fbd2801cae6c7337570601712fb81abfc25d9a43464541e13bc42b02f01f8ec78a7e5dd3e84fa9576891397106427a6ef262e11f24a55af39caf98130c69bb042475834753518d2f67f66c04d81c574eaf7d8b83bb029f037c4999159186e170752880638619096dd852f29994be72f3a6922a97610fa11085d4288214fe131d2243929d40dd5915a6789c77a499f43489ec2a0b7ad37e7b5070000000000000000dcdeed4e7121d043c65c8049da787baf0bb29c59b75e9608dc59b97fb001c92ffde01cdc9b9d97951e4247bb513ff71eff5413fda3522232fb7d7bc48429bf301c3bbf60c1e5346e6b2412ec70d7ec4091384f5a9cd1feddbf9b02fa15e591c1f86bb9ee9b65bccc11ed903646e771b8d252e63306498d3b325c7cf46dd9e4f8e1e385bd5631b11f427848e7fa3bb78f8f97d090ff15457ddb0030fa9a8332dd2ed68ec267909771b6ee3edd3370ba34516a4a7171680d2d912e2fa4c966942b231609e3a4f5860f35991d117445464ebf6049e31d0b0eacd23a11122c2cce82c6f72a160e131c588770bc6df3888503df8add4f1285f1417543f380479052a2bf8597f56b0d5587578c3b896ed7bf0e057b9a39ca8ee3b1abc2bb967bf5fd7064150871024113211114f203ce5f150e30ff55747a6ecd17a342b77fc7c41ba70e1a216112048b26bea002606abde584d49e7cd633b7a5391290e7808978100e2d5f955cda22772bf642aa0e3b4e8f30edc8c9e6dc1d4952160377205a83730dc58516ad5d5f1b121f397aa2d43f47ff7a09b602b9c61958a3317d2316b9c8db14e40a53e88f1744655a81b850ed7c9388c5388da56e36ed67ec15e15b52cac4bf47a088df916d2c70c9bb348640cb429cbed26810d8fe2218424a6c63f1dfb5bfd3277bc64dd12ae6c088d7f55c4804f6c8bcfd6332bf4d6edbce09574a9c64d3fdb1e41c4d17a403646efc749f7ca43eaf94b014dd269c833ac3e19bc7442ab86b815a9eaa9efa44d01e69c77f73f600c5a911f6e50b5713d079c855ddcc2ba6462077f5d170cda9bf0eb8e477095aad7cd5c081864b328a9cefe21b44b53ae46d38a66f6aeded011536310c3bc0dcf64d2dd0c9015c6c2f83d36d146f9bc9f5a627dca73c5e092953815715ce9ad107d5450b84d449e0e93bf16a4e03d8b15d8b938216cd6c0bc08ccbba0658391f8d8cccfcecf77a85778b9904105bce80cd8c15e38114a80fb6e6870fbc92af588a905da1de9cd58d7c288e7cf406ef916c27b1b4b61fac457fa6390d7a7b6c14256c7a87fd510806be27244e778173f993bf86ff5cbe48b85178c8ba1d529a974a00995ccaf42b64d4e714fb5df79980d79492002f7e220dceb7a14769cff9c1dba91247cb300eb8bfcfc3a85e360266fc4b9f328ba12a6098aeebc67e4fc9aae6762defa078251a0d9653cdbec24fbe31ffa3ef322e6ba7c7114d0f642b72c7ea2688505047248fb18b8321959dec8693a1ab349c16770aaf10889e46d8d8f508f3233a69c9a820b88fce5d5ed044b9cd7420f456459d3ae87a23cc72d3d9c770f94c82224d95426e10107ceb351676c1dea9f252470ec81a9825bf0c2b4a3342ffd702cdb306a351ef3bdb56539da5022b878e08549a6ec8d8773b44c19281da0e307614f72a30e46b73f8db627c4ae9f530c0ea6ea523a6b857f96acdf37b42808ffd31fccef667f92d7ae7ee853233308d0e6a61fb0f78f1d0ee35278788dc3f7585fff3688ce16d40da875b756b2cf4aa33875e01404fe7c74614f184a5eb458acd986abc580f0cf2517110b9f1239615194055de68c7925573faef91ec11706d27b7b672b42b323c32b25a796e795baa58a7dcf5e19a46f21b27bb14e2db080ea704f7a1d15c2ff114964a65bb7429a216ff96939999a743316b073d63cf87ec39d924f6e7658f325dd6a77c9921b2b21f49a22b1d96155d1dc9a206a9d521a5b3372c397556febe9495bbad48c1d9a50f0578bf5e0fdaf8d0276c1fbcd0eda0a5b72ced1fcddd7f6dc0df854aae139d42527db885aaed6998cfe1daef4865a39fccbf57673eae767a975e43f1b198185b1e37a7d1afe476cc35602f148cfee549147584f19255d6cb3e31def73cef31e3adff8184109cffcb6aba6e268367a2f1d803604aefe48404ec5b431c13dbb14374dc9e118736b43f342a3c93f57c707f58dab5f2359f88b48eb85c37d052105bdabb93a8e1f2866330f5548252ffbfd62b448fdeb777168701bdc6136a22bcc048e3679f6098c00ca7151267a4bb1c6561685f5f6fba0d1976a6b7999257a1e4d5155020b124f65e43dc06da593c7fdd96c6fd84afe493d2ade3624fd7672fc0fa7c77d97bea5be3c865655cc77440c7d28ea2cff6eb9bcf85780f2ff0e4215c8c18f63012aa4067fc1524e81c1b9d2e08975b0305c2a1add51a9471c9181835d923cf51b854cc659616fc1932e4997b2b3b737e661945abeb0d9b1fe3c113c2b2a8b371d8630927bcc23c21faff67fc6680ea0b3468b8a0279e3e160629bfccc7f1aac37b5aba4e275cb9cb8ada5c99361c70125a45c0536a9467343dbf1a22610ee2da7ab15fb8d3c5680cc447458f81523ee75668a3f75302693169b7a20349c35b77ef8e99cd3c8b852e4d1871972415de7b9bc9859697d7eab02e559f03cd57fae9e5d3d692e617a2cfbdc34eee3c9db4efdad6f1fb19a7c4907db5173f80ec204fe16919bcca832722c58273f8fb67f69e5d8b24c285aa1a74581f0f9d1fc11b42f578a1eafe7a7dc2c6f11065697d3207585344122314bcd914733132cd0bf5a661eb329dae384c0a85f559932a49d31facb17189716a38b20e3f0c0f30ab4686e79cfe9ad03c00fb0d726991869b6c89ea25da9450c3d6cc5bba1bb17c7a3c38962361890e3f7e24ce94253f63e12dcff3e2c3d045ad05f45ad9349575c7ccd0cd82fdf1e083c56dfed867382a4cf758decef9c05bea3138ca4507b7f638edbfe8f1512911ec00d4379fa996b4d64060078b95064cc81e92aff21e4b30d0b848a8d8d5ceab8f665e686881a79bb390d75f94c593be007bcd38d10de1e750df1a9256375c15e2bed4da66082248abb6d6660ec9ef6351125c245527c3b13e22d77ab516f2457d890f9dd5a6c8d0a21a4d626a5fcfc4bc3a427b5d3581830e070fc6b4c0ac8038bfa1aa52b12b0329410b6b8d0d78407817ec0cd708eaa5215921b14e113e8ceee38318fe47f3cf5a58c1ab86a6eb7734170e55b5002ab3a48cefe62fc6c897aeae4ef82eee968be5b59bf329cca3d03ad5dd38dc287eeda31de96cfb4ee94eca046b1d9e83632f1ce043b7c65782940b60bc0f9f35cde82d5623c83c6d3540139ba820af7bdada01c22a2531b1e3171e6befc5b8289868ccceedb49fe28a32bee69055d5e167eeaeb320832e0da67d6f536b7ea5226e86420f72ee68978a460d1a5f5d0f22e7b3fb59f68489f757581bb4fa107c14829bf5b93ee95f76a84c2f08514f73aa5c062585b57b02de19dd4039afc4480d8666cef6de93ea111a934295350433ecb4d7cd9957a0cc739439bd5308449d3ab744b76f429b3997611e9edfcc49f021f65645ac5524ddbc8fe9f49d8820633ea37d9e9dd5bfef0912fdc1ba80500df637ca3701122f543e99df4f528519c6233fe9cc94e5d2591124212b6a7711cb083018647b0e7200811aa58b82b5fbb10d347fb64ddf7ecc1e526d69bf7d0bf9ac4287ac42db1e4e1d9037ed3d9624cd19c590c460c87c71e7c5055cf0b78318761a5e5b8ea36978ac18275470e04d8e3da440cc7fda0b2fb7857bc2a0bda4843a60d21c3bfaaf7e32f16de155a161e01392be4ef0ab5df2bf0b18a0aaddcc364acf987c625c20fcb90b22e3a0bd6fdd161780a58517012cbfd7086a042f1e13b3f337ed2dfb4f66635d188287bfdadc3bab7a139c3ed8d784c2773836618e440f2f5ecabb712c9116a0d8536419ce663402b427556e899d12be13a588c66565c60fa3ee42f5b21cd3a8febc2b906eb91a778baf31aceeb53acb3556007cd7752fcda896c3a4a41cee337e5e63a5bd7d3d9be234295ccc93a1cd3b4c171f3a7210306901bcaabea6776909baa057ec840813de6ca414318b10d18787403f9ae1a57d671cecb824163683d2e8f3d40cd916a9e6d63aaf5f69dac13bf6cbcf9562a915febaf7d95e8fdc956018e42276703719e4f0d7c698051290d59531e034f884fe7794175006fa69b6b09897979881187a31d33c3728eb87e0562213ae81f502108314c35d590b02b4484caf58925d3f9620e89d5e4272be9fc2bcd587d337de2b815b64ebb3dc542dda0e64ff6d6037fff10941f565cb6814ac3058945d4fdd79c3f97819906551441b1914a4b6c4346a34d7d05315eeffa813cf95b83767317386bc21f1456ccf52cc983777764c02dd9c5ebf3940c19c8d4cd2c1e6366935211884e8aee011aac2fddcb0646cfb290d3f7ac0ea8fd1ec20b57f67a28a470673c4c202eb57409ca729c4ffadcb1828daed09ec1223d758548e477e7e06f8d9015df9a40b964438b2c59261a8527d0755b468601d381e60826627dad42f680f33a83027246eaa154ae2cf04ce7cfd0eae34735da1ecd1b408f4d41a9278115695b16a248cb697366105585a863b4629f6a6899d77dca911091f73e33a812f5baa98b3460edecd6cf2bda734810a7412943426e8d8e00e24afacc681379b92978ec8e049f34b22de5488e9ab25a7bd135ddcb766dcc95ee688268ef957b83130c6869bcf0bf43d606209c5071b00e32a20cd6da4bd4cc5492f435a62561348e3769093639c533570f22b9ecda5aa7c2773753211c68672101694fa491eaec2005a9cc439f773801afe423675efc54e302fdb5e09b895316e7ac8898343bcdd91ad238211e3318ddfe0d86c5ae1c6d83d791c52d9b35496b8ed9978a1c7b5dd00bc6c9697f39247238c2042258025121816660ef0c53a49995cd9b45ba1712d45695472b69d6757e56c572c0f290ad6225d35ca5e5f564ad2c3ac7fb0aa41ac346ec3036adaab5000b9c58d8861aed031b0e627a1e665c36dfceebc558f59df5b6235ee823de9d185d171a9237a20811d9b5e4efe508fb9907d25b6849dedff9e8d71fd0b2cff2f8ea8cc1a7e98b204295f267dae98a6476d99f9eb73499fc918e2db191b9decac79eef1b046e9958c32ff1c8bca28c125b2b2340f980219d5cc979eeabf2b2dadec09e43ea4bb67b136dd30cfcda9dbb611d899e5c3656d46bca5b43affc771c9eb1697b10936d6922c17baf10888d8fc10b4a891d4bfe5ead9136c4f79215c61d4796897fb39834f0440db29202211d82d2b16e69d9398fe33e22959a4310d274c67a4dc9ae6acf72abd13a1afb5fc319c3d5ae89933158c91ffc851e5e5fc854b102a047a0d30211afda1cb548d279fc894f1002c3721e3229519b560f0a71bb648149bc763b3401b9ad57704139a85d936bd0879a820f90be6ea7d78b6fe1679d336dcc2f776a3373e473ab5ee54f5e0a8df6114fd0e2d88ed6e7035d232eae1e4d785b417a43e06bdf7bfc3509bb4ad808d39ca785673436a1009dcce6cd055f10439de64652c31dcac2b65ec47264fec73581f8598f8c318bc0f8d0eb929e7c4d6e5f012238e01e19b1fd6b54feb463912e6557b65741ff8919434e44e04e41a5caadb74f3cc0254541c06faa480499326b7146499b55a2fced4dd416d5a31779c8de5dea938385217832cbc605788ff4fa96e24fdc3875b52769db18abdc76a687974925a52081043864ee8e39574d7f27fac30650eabb1fe70d5d7bab946b234d1b8ee449f08654ac868a22839924b9e7b94175e60c5b575263089b525f40d6f76570d2740095a43696a90f486332f45f4f6ee2d1d55d4b3f2311637a9e73f921a56ea0529104573b25b2c48307adcbe3b2232cc04933dae07c34c491099c57dd7e4b393fe34db10623bb35b74ce92f8b1892d24b9fd88eb7cfc3d1791b0bc3bddebcf42592093e6ffde9f40766ad3c21a67caed25b8b250946ec46773565fdcc6305803362274e612308b8b767423afffdb27239f3b9bcb8e70bf0ee68cc8a1af4e59f6972d7c90b182d862066d1b83ec044b259b398840cc736ece77dac61d07e4b338083a97146137dce132a2a8a0255d1c5aea80b5e0095b6181dbdf1fa23317c74c0e25cb86b08c4136065729777527f49f407256d36049e4c236201174b312e80090376a2113d62058b659f63de5b8695e52fd178c573ae0e14f205cb2fd929c9f09079930738deea7a2090288911af17e157d6c9ea4bc04f6264c81e4f3b5b087fd17a7c0b7eb631c1d1476ddce8be398fb7f576f3f01f98b823b7e8eca7cb3831c0ed3c036219c94ecf5a0c2d112f788d73a432394247fc0244de10e28d4577ecc084c521f0007f3a6215a549abb3091e59c6d6cd674c8e5775acd276c980033df3c2143be0589f65da81bfab1dff81e6783f814980bed3cf47e51dda6424ff43c2a82e966c7ec5b197a73cc580919db591cc522f87a561ca46753679b8d26110d1667bbcc24706633f219617f620958cfc35c1d70e034c0555bf76d9d854f8e5a0a973b88cd6096e5f3d2a72f3196f4bd42b32d6849fd37d10df37225b0cfe05c1005ff528c59352ad6e319f77da2ddf1c94b8b7e7c7a25c7cd15121540f31ec51725e71924c3616257756be8d404b53cb3091c6c3a11940c44cc8808426305550f35f9a4c5322222e661561bdecd8ace024f4c2fffeb3bc1bfa7f69454f6699d36dc42f35d78130f04b39ef392c5969a4d8de6d5271ee7a32af3f311386857fc0c21d053bae00a066ada63a28fd84b0fae439f1060a7b633ea07813863162195125e00d9c2aa7f25c11c981794cc97718f22e3cdbd21701d6f6f5fc25885be6330ac8b8266bd64a227535648a926a002345e224cb6bb4ec6767c1f1511c2c1753def927f97f9fd620c27eae292cca484472b6fe5d68aa58d3fbc772153043073308ec7c0a37c5355c0bdd43179025aa0e8d492353849fd7accf1a0acb7507f11281ab671d69ceaabbf539eae572f0015426f5e9a57e8c4d4d92216a93027572c11172cfb505e590e4d5899aa663a5ea32d5cc163ebef26c92908e15fe3c0705d06e32504c7901bebbbe7de2bb3f5ec28dd50299d941b24242ea0e151d747e129357f4fd6c98876dd0779fe40a7cd386f198150b43d5fced11d2906915937905f104a9ab5f0d05ddf227110e72a6c80a7878e559b5d8b1fd632e851633256254a22608358228feba5055a91dcfa6f702d55b8a463f0e0dd4af11872694e1b38ce25e8f8b4ec993d38223eba5dbc585fdd5ba53b179c20a278181fe3a0d13a7feb333941b59748b2b06b2c51d6c52d0a8bdcf833628cc30787d9d16c0991bb5a523a92da788563800133ac376a6feb09a8ce1de5c1ac1ce128272cdacb42b9592611e34bb0848a05f3c69e274b33481fd2b351ce7562413c2b2eee4cde66944b64103734d44e69b9187d0c98ee204f539c59b2374eef044e731b1bb3d06465f37d16634579e24f7ba512a29204e0e3fe7d89809914e41b7ad8d17e5ac11af5246b2192c833af76bad3906f95683240ea619360d9a0ad9e7e97777e2797180447e13067cdd4efb5ee7429fc5b97e80d3f9b8396b8d2df51ac47b434ee0109b0eaa22f854d4de4ada9dcc003a4a8e147ceed375d607922b59a50312813519571e2a526774b1241513f77d2b53d3d435f141c55eddad14021ccc542ac37aea8e24b9331fcceaa35dec147c114e181e9a6800f1748c3aa1b425d532da7ed19bf10ec5304900d6f685acba0fed0d7bde86d5d5dab0fe046efe66fe78b886d24c097fa252a7a9151e7ba2cf15798d1cdc07904c8f32c1853b8e79be4d344b6d8820005ff2e11ceaa7833e1de67fa3f64637bf20963bea0a981ee810562a0b8b2dd532e01140421a975d2e7f39c3988e99e5cc8116cd9462e2f405121dea5a03616d7fcad17ba0956b84671d94c524efaa13b6b0bcc15b98b6433619bbfc9736d5432fc5237b9a018b716c2e08e4eac57d2b7c33ffd813816a0b01f7d09a5afa082e1d10a327247e0f086f3360dd6b9b736903a518390b887a6dd2f5cfa65322e5fe6dd801ce6ff44388ecfcc02ab12c562f55484cbda45465049da5aba32131491f3bfd92a1f9e85fef13e622e06445252fe64b42680ba24d09d0cf28c9f517a24ac14322d57761c1d53491940ce65df7c1ce7b6f766409f66f6ab4a9b455df1de0316ea2281d48f8f2bfa74f001509e7d2222b7931453759b1cb0ef887335e62bf97aeb1fae49095be74c11f89ae74297e22a342f60ff851f43c1e695080527747f9326097ef0a6838a1f46daab1f109c4e921beb2ea1cb6a334dedc57e55e58469aea61a185933a8a4c5fcb5366487d36541c46f12d8b830ab05c415cdafeb875eb2031e154fdde2d07437c0297ba56e4416cab08544013b2728a42ac6daac513c436f3e07dc6aeee0957297b17c242e221c41187df1fe919602d84805486c0dfe8ba108bc4c5b95c300bf121307d465a3b69f99fece83b1ddba11e3a8ebde80cedaa52c7b5acc9d0417064415bfb6d8ff6ff46444a62619c9321171883977194fddaa0fb059c29139fea48955b1ceddae3ac8051923472d23f06773f49e6624a8f2ba23b36a6c574d85b40f701d705c983ad5bce01bb2d031617bf8b2769ffc90d2ef086be32b2e84cad752afdee010c8e26ee09ea95e963b57e93188d4af84a71a5224e383a0dde184ed343fc59f77620045b9d6cffdb1a89ef0d5cb7e853810f24523a211f2a2fc19a49729d02fbc51b9674783ed4c7c0ef070c6b47413681ee65934cbc65777f92d13f01879a5851ca57501b5cd016cce8e9902f4379997d5c75b3558161552ebe9eb325a3ee76fe87bfde3d2cb44fb9009bcf14ea07900a9e120367d6a0253fba1b57f388efab647bcbad82a4ef8b5a7158c3d0ddfd134e568c937375a9df0304080a5ae883ec3661b176c272c7dd87aa40f209c9fef9d07bd102818b55f3e7a8c0e06dad914acae44f9023a04395e12b1d41b7d425634c748713a709547376cff51e864654c3b24b19d294b99c5e61abbc2f270163995512a5d042d94e1df1b49bc9f2fc814d7409f97758487ea49b276ce60ba980b9e2df67e1a186a18ea60ac743f45714b80eeb90cc06d7158f6cf09c54a858fef698ffe7032c22de1618bfd90bbe8dd7db0aba29f53abf0aca67e1471bee1c871bf3595f423213645b9dd88a4153c7dee3845194b496abcc0104997fc89eebd4fc72c5ca73cb5789cbbec9cdd9cb0cf9117718041860fb2073fb3842c45cd1a8e44a295b3166e1ab0a5a18aaf22575454d4d3750aa838944d1f5caf671d9402cf331785d83c50c29b2e49d402ce4ba32516fe1b37c463359d7cb780d3561ac8c6ecf1626d33aa1b482f343086e740be27175fbe1cc6ff2f798b9cc96e2d88baa6d80b0e443374cdc7f11cda7def2a1875595f6980d0779641c9777632f7a5aebeb48f3e9d01c187695217de185ea6c99cd47b321c53c26a83cd2f749612ca143a2a3a594c2e80c1ec90d98564f2a69e579f9713c20af6a559f541514a7514b8fd88165be486e825c3de6899e97f9e39a1654e51ed9690cefd5e225602e68480e9f2346b3f8d61268dc5f6add54484b7f4bf8fe086110f74183a0bc2515056f9a0bad6f4dbbd848d80df7a24d5d92a6b6b2b16fabc0486028a13b67a2bbe17f9812ccf6692327e78a5dfeb56feb6dda38c9ff2360062cf7128f357ab4b395b26880fdf80dd889ab809da70b1f58f5c1005a3e054f9c40691c287163f445610bdf0f14e6cb73e0eac5ab2fa5a9db4a9774c135e92200c27a34aaa2f3b9e2d568d28ae69652f1a351636492a590f79df0d4f9d589ba0651aa572a6cf9070a873ce75a200506ca74339dafbf0da6add65d5b5594b5b49bb0b63b398b44ff5f996115ce0d1b6f220b7b7b20049f5fc9f26e4fd8b7668578c4dda8365a9e79db99378091b3572473747ca3b4104e7b7bb1bc22988df73cc1e6fdb242c78804b4334550788a3e6a50a17fd8a0985f2449eb8554bc28c989cc1d9755aac8b33482d2c70525e102b4518f7aed260cbc6d6e7317a22e92b776183ccd105ac9bfbed1bbf0da4be0c0203a390cb9b488ed0aaa098ba8d5ebb9659fbf19b8cbc1b4d78edae8f5f272d2bf0303fdedd0e3de90ec360fced0ebbc85d612a9c396ba66cde481b92efc7ac9a1381dea34d170791896ea030cfce7234a0a2d2301bc79043aa7dfed6f3ea7bbe28a344a274fd9b9670b70cd74379a6fa1849a26609f1ef892c0073b7db34f09b8631af1884552a234f7f37263e04c61418b602bddc8561c0e097c5767fdeddc5deabac6308e5ae1c41565baa4d0bb33718486f3c655245f26b26a12becdc95a31819afa5729cbf3127bca5596f9d409301a32e80a3b7ec270b9387e88e9e1422b9ac903aab06f29970b50673222a3460acc1ed2a6ed45d10d3b42887d803e880f39141ce63b8d3fcaf49d87a9ec7f9720808aeaf8eab0ab37ab690f97cbc125be08fd6cd41df957059878d19b106b39e06bf30cc9d3c85f25de650f4afd295dc11025f9a72a5e46422d88c01972e22bf024fe61e0dfb824df1bf44d1f77db0118127ae48e2a145d82bcb537cc4be81e7bbe0e1d3e19b56537e7bee8931d4fc38a03d1c387079b64590d1f775566cf0a16ca2bbad3409c29ca616d8f91040a3ef52bd0b7fb2dea0b65f0841a03fcf8d25a6d7a0904a74ca61835e3e0734e4addddd167cbe9d3ccc89a0965191fd70065b0fc48f450ebd036f9c15039393ebd191a982fff506847cb3de8eed4cb8074da6076302b1e7623cbf5bb1ac4d6af09fad853100eb1aeb302717031608283897f3edb7e5b7f5768a363eb2bfe951b6a9c470d9abfad6ebabd17dadc0ec021684726a3f7daceb4acfed42084e70c8e721034f481300", From 29af613ca4599cb69d31e44b4bdf642545e9e973 Mon Sep 17 00:00:00 2001 From: Dmitry Demin Date: Fri, 6 Dec 2024 11:55:56 +0100 Subject: [PATCH 32/35] Add amount method to BurnItem and make BurnItem pub (visible for other crates) --- zebra-chain/src/orchard_zsa.rs | 5 ++++- zebra-chain/src/orchard_zsa/burn.rs | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/zebra-chain/src/orchard_zsa.rs b/zebra-chain/src/orchard_zsa.rs index 00a27360742..d1e55db98b8 100644 --- a/zebra-chain/src/orchard_zsa.rs +++ b/zebra-chain/src/orchard_zsa.rs @@ -13,7 +13,10 @@ pub mod asset_state; mod burn; mod issuance; -pub(crate) use burn::{Burn, BurnItem, NoBurn}; +pub(crate) use burn::{Burn, NoBurn}; pub(crate) use issuance::IssueData; +pub use burn::BurnItem; + +// FIXME: should asset_state mod be pub and these structs be pub as well? pub use asset_state::{AssetBase, AssetState, AssetStateChange, IssuedAssets, IssuedAssetsChange}; diff --git a/zebra-chain/src/orchard_zsa/burn.rs b/zebra-chain/src/orchard_zsa/burn.rs index 6fdda20b1bc..fbbd1787ce4 100644 --- a/zebra-chain/src/orchard_zsa/burn.rs +++ b/zebra-chain/src/orchard_zsa/burn.rs @@ -49,6 +49,11 @@ impl BurnItem { self.0 } + /// Returns the amount being burned. + pub fn amount(&self) -> NoteValue { + self.1 + } + /// Returns the raw [`u64`] amount being burned. pub fn raw_amount(&self) -> u64 { self.1.inner() From 2b7926a7bce0840a0536448ce111bf1cd0cf7657 Mon Sep 17 00:00:00 2001 From: Dmitry Demin Date: Fri, 6 Dec 2024 12:26:27 +0100 Subject: [PATCH 33/35] Fix Orchard ZSA workflow tests to make it compilable with getblocktemplate-rpcs feature enabled --- zebra-consensus/src/orchard_zsa/tests.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/zebra-consensus/src/orchard_zsa/tests.rs b/zebra-consensus/src/orchard_zsa/tests.rs index cda60d24018..2eb1816e304 100644 --- a/zebra-consensus/src/orchard_zsa/tests.rs +++ b/zebra-consensus/src/orchard_zsa/tests.rs @@ -80,7 +80,12 @@ fn calc_asset_supply_info<'a, I: IntoIterator>( ) -> Result { blocks .into_iter() - .flat_map(|(Request::Commit(block), _)| &block.transactions) + .filter_map(|(request, _)| match request { + Request::Commit(block) => Some(&block.transactions), + #[cfg(feature = "getblocktemplate-rpcs")] + Request::CheckProposal(_) => None, + }) + .flatten() .try_fold(SupplyInfo::new(), |mut supply_info, tx| { process_burns(&mut supply_info, tx.orchard_burns().iter())?; process_issue_actions(&mut supply_info, tx.orchard_issue_actions())?; From 73c804f6bbe96f6f0e15cf3fd1665f6d42ab522c Mon Sep 17 00:00:00 2001 From: Dmitry Demin Date: Fri, 6 Dec 2024 14:21:40 +0100 Subject: [PATCH 34/35] Fix clippy error --- zebra-consensus/src/orchard_zsa/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zebra-consensus/src/orchard_zsa/tests.rs b/zebra-consensus/src/orchard_zsa/tests.rs index 2eb1816e304..b2220835d79 100644 --- a/zebra-consensus/src/orchard_zsa/tests.rs +++ b/zebra-consensus/src/orchard_zsa/tests.rs @@ -140,7 +140,7 @@ async fn check_zsa_workflow() -> Result<(), Report> { calc_asset_supply_info(&transcript_data).expect("should calculate asset_supply_info"); // Before applying the blocks, ensure that none of the assets exist in the state. - for (&asset_base, _asset_supply) in &asset_supply_info.assets { + for &asset_base in asset_supply_info.assets.keys() { assert!( request_asset_state(&read_state_service, asset_base) .await From 6bd42845461e9027118a8f62bb71290f4ce3aacf Mon Sep 17 00:00:00 2001 From: Dmitry Demin Date: Mon, 9 Dec 2024 12:54:18 +0100 Subject: [PATCH 35/35] Add rust-toolchain.toml with Rust version 1.82.0 to avoid clippy errors came with Rust 1.83.0 --- .github/workflows/ci-basic.yml | 14 +------------- rust-toolchain.toml | 3 +++ 2 files changed, 4 insertions(+), 13 deletions(-) create mode 100644 rust-toolchain.toml diff --git a/.github/workflows/ci-basic.yml b/.github/workflows/ci-basic.yml index 792a60aa80f..e52c70103b6 100644 --- a/.github/workflows/ci-basic.yml +++ b/.github/workflows/ci-basic.yml @@ -35,16 +35,4 @@ jobs: - name: Run format check run: cargo fmt -- --check - name: Run clippy - # FIXME: Temporarily disable specific Clippy checks to allow CI to pass while addressing existing issues. - # This may be related to stricter Clippy rules introduced in Rust 1.83.0. - # Once the Clippy warnings/errors are resolved, revert to the original Clippy command below. - # Original Clippy command: - # run: cargo clippy --workspace --all-features --all-targets -- -D warnings - run: | - cargo clippy --workspace --all-features --all-targets -- -D warnings \ - -A clippy::unnecessary_lazy_evaluations \ - -A elided-named-lifetimes \ - -A clippy::needless_lifetimes \ - -A missing-docs \ - -A non_local_definitions \ - -A clippy::needless_return + run: cargo clippy --workspace --all-features --all-targets -- -D warnings diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000000..30e035b8e74 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.82.0" +components = [ "clippy", "rustfmt" ]