Skip to content

Commit

Permalink
Allow finalizing issued assets via the issue action when no notes are…
Browse files Browse the repository at this point in the history
… provided and the finalize flag is set to true
  • Loading branch information
dmidem committed Dec 21, 2024
1 parent 6bd4284 commit 4830e56
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 77 deletions.
70 changes: 27 additions & 43 deletions zebra-chain/src/orchard_zsa/asset_state.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
//! Defines and implements the issued asset state types
use std::{
collections::{HashMap, HashSet},
sync::Arc,
};
use std::{collections::HashMap, sync::Arc};

use orchard::issuance::IssueAction;
pub use orchard::note::AssetBase;

use crate::{serialization::ZcashSerialize, transaction::Transaction};

use super::BurnItem;
use super::{BurnItem, IssueData};

/// The circulating supply and whether that supply has been finalized.
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, serde::Serialize)]
Expand Down Expand Up @@ -183,52 +180,39 @@ impl AssetStateChange {
/// 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<Transaction>) -> impl Iterator<Item = (AssetBase, Self)> + '_ {
Self::from_burns(tx.orchard_burns())
.chain(Self::from_issue_actions(tx.orchard_issue_actions()))
Self::from_burns(tx.orchard_burns()).chain(
tx.orchard_issue_data()
.iter()
.flat_map(Self::from_issue_data),
)
}

/// Accepts an iterator of [`IssueAction`]s and returns an iterator of asset bases and issued asset state changes
/// Accepts an [`IssueData`] 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<Item = &'a IssueAction> + 'a,
) -> impl Iterator<Item = (AssetBase, Self)> + 'a {
actions.flat_map(Self::from_issue_action)
fn from_issue_data(issue_data: &IssueData) -> impl Iterator<Item = (AssetBase, Self)> + '_ {
let ik = issue_data.inner().ik();
issue_data.actions().flat_map(|action| {
let issue_asset = AssetBase::derive(ik, action.asset_desc());
Self::from_issue_action(issue_asset, 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<Item = (AssetBase, Self)> + '_ {
let supply_changes = Self::from_notes(action.notes());
let finalize_changes = action
.is_finalized()
.then(|| {
action
.notes()
.iter()
.map(orchard::Note::asset)
.collect::<HashSet<AssetBase>>()
})
.unwrap_or_default()
fn from_issue_action(
issue_asset: AssetBase,
action: &IssueAction,
) -> impl Iterator<Item = (AssetBase, Self)> + '_ {
(action.is_finalized() && action.notes().is_empty())
.then(|| Self::new(issue_asset, SupplyChange::Issuance(0), true))
.into_iter()
.map(|asset_base| Self::new(asset_base, SupplyChange::Issuance(0), true));

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<Item = (AssetBase, Self)> + '_ {
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(),
SupplyChange::Issuance(note.value().inner()),
false,
)
.chain(action.notes().iter().map(|note| {
Self::new(
note.asset(),
SupplyChange::Issuance(note.value().inner()),
action.is_finalized(),
)
}))
}

/// Accepts an iterator of [`BurnItem`]s and returns an iterator of asset bases and issued asset state changes
Expand Down
11 changes: 1 addition & 10 deletions zebra-chain/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1099,7 +1099,7 @@ 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<orchard_zsa::IssueData> {
pub fn orchard_issue_data(&self) -> &Option<orchard_zsa::IssueData> {
match self {
Transaction::V1 { .. }
| Transaction::V2 { .. }
Expand All @@ -1114,15 +1114,6 @@ impl Transaction {
}
}

/// 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<Item = &::orchard::issuance::IssueAction> {
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")]
Expand Down
79 changes: 59 additions & 20 deletions zebra-consensus/src/orchard_zsa/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ use color_eyre::eyre::Report;
use tower::ServiceExt;

use orchard::{
issuance::Error as IssuanceError,
issuance::IssueAction,
issuance::{Error as IssuanceError, IssueAction},
keys::IssuanceValidatingKey,
note::AssetBase,
supply_info::{AssetSupply, SupplyInfo},
value::ValueSum,
value::{NoteValue, ValueSum},
};

use zebra_chain::{
Expand Down Expand Up @@ -53,21 +53,42 @@ fn process_burns<'a, I: Iterator<Item = &'a BurnItem>>(
}

/// Processes orchard issue actions, increasing asset supply.
fn process_issue_actions<'a, I: Iterator<Item = &'a IssueAction>>(
fn process_issue_actions<'a, I: IntoIterator<Item = &'a IssueAction>>(
supply_info: &mut SupplyInfo,
issue_actions: I,
ik: &IssuanceValidatingKey,
actions: I,
) -> Result<(), IssuanceError> {
for action in issue_actions {
for action in actions {
let issue_asset = AssetBase::derive(ik, action.asset_desc());
let is_finalized = action.is_finalized();

for note in action.notes() {
supply_info.add_supply(
note.asset(),
AssetSupply {
amount: note.value().into(),
is_finalized,
},
)?;
if action.notes().is_empty() {
if is_finalized {
supply_info.add_supply(
issue_asset,
AssetSupply {
amount: NoteValue::from_raw(0).into(),
is_finalized: true,
},
)?;
} else {
return Err(IssuanceError::IssueActionWithoutNoteNotFinalized);
}
} else {
for note in action.notes() {
note.asset()
.eq(&issue_asset)
.then_some(())
.ok_or(IssuanceError::IssueBundleIkMismatchAssetBase)?;

supply_info.add_supply(
note.asset(),
AssetSupply {
amount: note.value().into(),
is_finalized,
},
)?;
}
}
}

Expand All @@ -80,15 +101,22 @@ fn calc_asset_supply_info<'a, I: IntoIterator<Item = &'a TranscriptItem>>(
) -> Result<SupplyInfo, IssuanceError> {
blocks
.into_iter()
.filter_map(|(request, _)| match request {
Request::Commit(block) => Some(&block.transactions),
#[cfg(feature = "getblocktemplate-rpcs")]
Request::CheckProposal(_) => None,
.filter_map(|(request, result)| match (request, result) {
(Request::Commit(block), Ok(_)) => Some(&block.transactions),
_ => 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())?;

if let Some(issue_data) = tx.orchard_issue_data() {
process_issue_actions(
&mut supply_info,
issue_data.inner().ik(),
issue_data.actions(),
)?;
}

Ok(supply_info)
})
}
Expand All @@ -103,7 +131,17 @@ fn create_transcript_data<'a, I: IntoIterator<Item = &'a Vec<u8>>>(

std::iter::once(regtest_genesis_block())
.chain(workflow_blocks)
.map(|block| (Request::Commit(block.clone()), Ok(block.hash())))
.enumerate()
.map(|(i, block)| {
(
Request::Commit(block.clone()),
if i == 5 {
Err(ExpectedTranscriptError::Any)
} else {
Ok(block.hash())
},
)
})
}

/// Queries the state service for the asset state of the given asset.
Expand Down Expand Up @@ -168,6 +206,7 @@ async fn check_zsa_workflow() -> Result<(), Report> {

assert_eq!(
asset_state.total_supply,
// FIXME: Fix it after chaning ValueSum to NoteValue in AssetSupply in orchard
u64::try_from(i128::from(asset_supply.amount))
.expect("asset supply amount should be within u64 range"),
"Total supply mismatch for asset {:?}.",
Expand Down
Loading

0 comments on commit 4830e56

Please sign in to comment.