Adds documentation to types and methods in `asset_state` module, fixes several bugs.
This commit is contained in:
parent
8f26a89151
commit
e063729bcd
|
@ -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<u64> {
|
||||
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<Self> {
|
||||
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> {
|
||||
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> {
|
||||
self.total_supply = (-change.supply_change).apply_to(self.total_supply)?;
|
||||
Some(self)
|
||||
|
@ -135,31 +161,40 @@ impl From<HashMap<AssetBase, AssetState>> 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<Transaction>) -> impl Iterator<Item = (AssetBase, Self)> + '_ {
|
||||
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<Item = &'a IssueAction> + 'a,
|
||||
) -> impl Iterator<Item = (AssetBase, Self)> + '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<Item = (AssetBase, Self)> + '_ {
|
||||
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<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(),
|
||||
|
@ -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<Item = (AssetBase, Self)> + '_ {
|
||||
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<AssetBase, AssetState>);
|
||||
|
@ -235,10 +269,9 @@ impl IssuedAssets {
|
|||
self.0.iter()
|
||||
}
|
||||
|
||||
fn update<'a>(&mut self, issued_assets: impl Iterator<Item = (AssetBase, AssetState)> + '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<Item = (AssetBase, AssetState)> + 'a) {
|
||||
self.0.extend(issued_assets);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -257,10 +290,12 @@ impl IntoIterator for IssuedAssets {
|
|||
pub struct IssuedAssetsChange(HashMap<AssetBase, AssetStateChange>);
|
||||
|
||||
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<Item = (AssetBase, AssetStateChange)> + 'a,
|
||||
|
@ -274,20 +309,26 @@ impl IssuedAssetsChange {
|
|||
true
|
||||
}
|
||||
|
||||
/// Accepts a [`Arc<Transaction>`].
|
||||
///
|
||||
/// Returns an [`IssuedAssetsChange`] representing all of the changes to the issued assets
|
||||
/// map that should be applied for the provided transaction, or `None` if the change would be invalid.
|
||||
pub fn from_transaction(transaction: &Arc<Transaction>) -> Option<Self> {
|
||||
let mut issued_assets_change = Self::new();
|
||||
|
||||
if !issued_assets_change.update(AssetStateChange::from_transaction(transaction)) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(issued_assets_change)
|
||||
}
|
||||
|
||||
/// Accepts a slice of [`Arc<Transaction>`]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<Transaction>]) -> Option<Self> {
|
||||
let mut issued_assets_change = Self::new();
|
||||
|
||||
for transaction in transactions {
|
||||
if !issued_assets_change.update(AssetStateChange::from_transaction(transaction)) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
Some(issued_assets_change)
|
||||
pub fn from_transactions(transactions: &[Arc<Transaction>]) -> Option<Arc<[Self]>> {
|
||||
transactions.iter().map(Self::from_transaction).collect()
|
||||
}
|
||||
|
||||
/// Consumes self and accepts a closure for looking up previous asset states.
|
||||
|
@ -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<Item = (AssetBase, AssetStateChange)> + '_ {
|
||||
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<AssetBase, AssetStateChange>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)?;
|
||||
.map_err(VerifyBlockError::Transaction)?
|
||||
else {
|
||||
panic!("unexpected response from transaction verifier");
|
||||
};
|
||||
|
||||
assert!(
|
||||
matches!(response, tx::Response::Block { .. }),
|
||||
"unexpected response from transaction verifier: {response:?}"
|
||||
);
|
||||
|
||||
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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -31,7 +31,8 @@ impl Prepare for Arc<Block> {
|
|||
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<Block> {
|
|||
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 {
|
||||
|
|
|
@ -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<Amount<NonNegative>>,
|
||||
/// A map of burns to be applied to the issued assets map.
|
||||
// TODO: Reference ZIP.
|
||||
pub issued_assets_change: Option<IssuedAssetsChange>,
|
||||
/// 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<IssuedAssetsChange> for IssuedAssetsOrChange {
|
||||
fn from(change: IssuedAssetsChange) -> Self {
|
||||
Self::Change(change)
|
||||
impl From<Arc<[IssuedAssetsChange]>> 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<IssuedAssets> 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<Arc<Block>> for CheckpointVerifiedBlock {
|
||||
fn from(block: Arc<Block>) -> 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<Arc<Block>> for SemanticallyVerifiedBlock {
|
|||
new_outputs,
|
||||
transaction_hashes,
|
||||
deferred_balance: None,
|
||||
issued_assets_change: None,
|
||||
issued_assets_changes: Arc::new([]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -572,7 +570,7 @@ impl From<ContextuallyVerifiedBlock> for SemanticallyVerifiedBlock {
|
|||
.constrain::<NonNegative>()
|
||||
.expect("deferred balance in a block must me non-negative"),
|
||||
),
|
||||
issued_assets_change: None,
|
||||
issued_assets_changes: Arc::new([]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,7 +116,7 @@ impl From<SemanticallyVerifiedBlock> for ChainTipBlock {
|
|||
new_outputs: _,
|
||||
transaction_hashes,
|
||||
deferred_balance: _,
|
||||
issued_assets_change: _,
|
||||
issued_assets_changes: _,
|
||||
} = prepared;
|
||||
|
||||
Self {
|
||||
|
|
|
@ -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<Chain>,
|
||||
issued_assets: &HashMap<AssetBase, AssetState>,
|
||||
asset_base: &AssetBase,
|
||||
) -> Option<AssetState> {
|
||||
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<Chain>,
|
||||
semantically_verified: &SemanticallyVerifiedBlock,
|
||||
) -> Result<IssuedAssets, ValidateContextError> {
|
||||
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()
|
||||
.apply_change(change)
|
||||
.ok_or(ValidateContextError::InvalidIssuance)?
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
issued_assets
|
||||
.insert(asset_base, updated_asset_state)
|
||||
.expect("transactions must have only one burn item per asset base");
|
||||
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)?;
|
||||
|
||||
issued_assets
|
||||
.insert(asset_base, updated_asset_state)
|
||||
.expect("transactions must have only one burn item per asset base");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(issued_assets.into())
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue