Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion zebra-chain/src/orchard_zsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ mod burn;
mod issuance;

pub(crate) use burn::{Burn, NoBurn};
pub(crate) use issuance::IssueData;
pub use issuance::IssueData;

pub use burn::BurnItem;

Expand Down
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
112 changes: 84 additions & 28 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 All @@ -24,11 +24,15 @@ use zebra_state::{ReadRequest, ReadResponse, ReadStateService};

use zebra_test::{
transcript::{ExpectedTranscriptError, Transcript},
vectors::ORCHARD_ZSA_WORKFLOW_BLOCKS,
vectors::{OrchardZSABlock, ORCHARD_ZSA_WORKFLOW_BLOCKS},
};

use crate::{block::Request, Config};

mod block_description;

use block_description::build_block_description;

type TranscriptItem = (Request, Result<Hash, ExpectedTranscriptError>);

/// Processes orchard burns, decreasing asset supply.
Expand All @@ -53,21 +57,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,30 +105,60 @@ 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)
})
}

/// Creates transcript data from predefined workflow blocks.
fn create_transcript_data<'a, I: IntoIterator<Item = &'a Vec<u8>>>(
fn create_transcript_data<'a, I: IntoIterator<Item = &'a OrchardZSABlock>>(
serialized_blocks: I,
) -> impl Iterator<Item = TranscriptItem> + use<'a, I> {
let workflow_blocks = serialized_blocks.into_iter().map(|block_bytes| {
Arc::new(Block::zcash_deserialize(&block_bytes[..]).expect("block should deserialize"))
});

std::iter::once(regtest_genesis_block())
let workflow_blocks = serialized_blocks.into_iter().map(
|OrchardZSABlock {
description,
bytes,
is_valid,
}| {
(
description,
Arc::new(Block::zcash_deserialize(&bytes[..]).expect("block should deserialize")),
*is_valid,
)
},
);

std::iter::once((&None, regtest_genesis_block(), true))
.chain(workflow_blocks)
.map(|block| (Request::Commit(block.clone()), Ok(block.hash())))
.map(|(description, block, is_valid)| {
if let Some(description) = description {
assert_eq!(description, &build_block_description(&block))
}

(
Request::Commit(block.clone()),
if is_valid {
Ok(block.hash())
} else {
Err(ExpectedTranscriptError::Any)
},
)
})
}

/// Queries the state service for the asset state of the given asset.
Expand Down Expand Up @@ -134,7 +189,7 @@ async fn check_zsa_workflow() -> Result<(), Report> {
crate::router::init(Config::default(), &network, state_service.clone()).await;

let transcript_data =
create_transcript_data(ORCHARD_ZSA_WORKFLOW_BLOCKS.iter()).collect::<Vec<_>>();
create_transcript_data(ORCHARD_ZSA_WORKFLOW_BLOCKS[0].iter()).collect::<Vec<_>>();

let asset_supply_info =
calc_asset_supply_info(&transcript_data).expect("should calculate asset_supply_info");
Expand Down Expand Up @@ -168,6 +223,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
Loading