diff --git a/zebra-consensus/src/orchard_zsa/tests.rs b/zebra-consensus/src/orchard_zsa/tests.rs index 04b0b355a39..50361099c36 100644 --- a/zebra-consensus/src/orchard_zsa/tests.rs +++ b/zebra-consensus/src/orchard_zsa/tests.rs @@ -7,6 +7,7 @@ use std::{ collections::{hash_map, HashMap}, + error::Error, sync::Arc, }; @@ -17,7 +18,7 @@ use tower::ServiceExt; use orchard::{ issuance::{ auth::{IssueValidatingKey, ZSASchnorr}, - {AssetRecord, IssueAction}, + AssetRecord, IssueAction, }, note::{AssetBase, AssetId}, value::NoteValue, @@ -25,6 +26,7 @@ use orchard::{ use zebra_chain::{ block::{genesis::regtest_genesis_block, Block, Hash}, + orchard_zsa::AssetStateError, parameters::{testnet::ConfiguredActivationHeights, Network}, serialization::ZcashDeserialize, }; @@ -36,7 +38,7 @@ use zebra_state::{ReadRequest, ReadResponse, ReadStateService}; use zebra_test::{ transcript::{ExpectedTranscriptError, Transcript}, - vectors::{OrchardWorkflowBlock, ORCHARD_ZSA_WORKFLOW_BLOCKS}, + vectors::{OrchardWorkflowBlock, OrchardWorkflowBlockResult, ORCHARD_ZSA_WORKFLOW_BLOCKS}, }; use crate::{block::Request, Config}; @@ -56,6 +58,39 @@ enum AssetRecordsError { ModifyFinalized, } +type BoxError = Box; + +fn has_finalized_asset_error(error: &(dyn Error + 'static)) -> bool { + let mut error = Some(error); + + while let Some(err) = error { + if matches!( + err.downcast_ref::(), + Some(AssetStateError::Issue( + orchard::issuance::Error::IssueActionPreviouslyFinalizedAssetBase + )) + ) { + return true; + } + + error = err.source(); + } + + false +} + +fn expect_finalized_asset_error(error: Option) -> Result<(), BoxError> { + let Some(error) = error else { + return Err(eyre!("expected finalized asset error").into()); + }; + + if has_finalized_asset_error(&*error) { + Ok(()) + } else { + Err(error) + } +} + /// Processes orchard burns, decreasing asset supply. fn process_burns<'a, I: IntoIterator>( asset_records: &mut AssetRecords, @@ -176,25 +211,28 @@ fn create_transcript_data<'a, I: IntoIterator>( |OrchardWorkflowBlock { height: _, bytes, - is_valid, + expected_result, }| { - ( - Arc::new(Block::zcash_deserialize(&bytes[..]).expect("block should deserialize")), - *is_valid, - ) + let block = + Arc::new(Block::zcash_deserialize(&bytes[..]).expect("block should deserialize")); + + let expected_result = match expected_result { + OrchardWorkflowBlockResult::Valid => Ok(()), + OrchardWorkflowBlockResult::IssueFinalizedAssetError => { + Err(ExpectedTranscriptError::exact(expect_finalized_asset_error)) + } + }; + + (block, expected_result) }, ); - std::iter::once((regtest_genesis_block(), true)) + std::iter::once((regtest_genesis_block(), Ok(()))) .chain(workflow_blocks) - .map(|(block, is_valid)| { + .map(|(block, expected_result)| { ( Request::Commit(block.clone()), - if is_valid { - Ok(block.hash()) - } else { - Err(ExpectedTranscriptError::Any) - }, + expected_result.map(|_| block.hash()), ) }) } diff --git a/zebra-state/src/service/check/tests/issuance.rs b/zebra-state/src/service/check/tests/issuance.rs index b7a54a41944..5f15e22f298 100644 --- a/zebra-state/src/service/check/tests/issuance.rs +++ b/zebra-state/src/service/check/tests/issuance.rs @@ -7,7 +7,9 @@ use zebra_chain::{ serialization::ZcashDeserialize, }; -use zebra_test::vectors::{OrchardWorkflowBlock, ORCHARD_ZSA_WORKFLOW_BLOCKS}; +use zebra_test::vectors::{ + OrchardWorkflowBlock, OrchardWorkflowBlockResult, ORCHARD_ZSA_WORKFLOW_BLOCKS, +}; use crate::{ check::Chain, @@ -59,7 +61,7 @@ fn check_burns_and_issuance() { for OrchardWorkflowBlock { height, bytes, - is_valid, + expected_result, } in ORCHARD_ZSA_WORKFLOW_BLOCKS.iter() { let block = @@ -83,7 +85,7 @@ fn check_burns_and_issuance() { let commit_result = validate_and_commit_non_finalized(&finalized_state.db, &mut non_finalized_state, block); - if !is_valid { + if !matches!(expected_result, OrchardWorkflowBlockResult::Valid) { assert!( issued_asset_changes_result.is_err() || commit_result.is_err(), "invalid workflow block at height {height} should fail issued-asset validation or commit" diff --git a/zebra-test/src/vectors/orchard_zsa_workflow_blocks.rs b/zebra-test/src/vectors/orchard_zsa_workflow_blocks.rs index 8aa976208fb..9e1f463fd5f 100644 --- a/zebra-test/src/vectors/orchard_zsa_workflow_blocks.rs +++ b/zebra-test/src/vectors/orchard_zsa_workflow_blocks.rs @@ -5,14 +5,21 @@ use hex::FromHex; use lazy_static::lazy_static; +/// Expected consensus result for a block. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum OrchardWorkflowBlockResult { + Valid, + IssueFinalizedAssetError, +} + /// Represents a serialized block and its validity status. pub struct OrchardWorkflowBlock { /// Block height. pub height: u32, /// Serialized byte data of the block. pub bytes: &'static [u8], - /// Indicates whether the block is valid. - pub is_valid: bool, + /// Expected result of transcript validation for this block. + pub expected_result: OrchardWorkflowBlockResult, } fn decode_bytes(hex: &str) -> Vec { @@ -41,35 +48,35 @@ lazy_static! { OrchardWorkflowBlock { height: 1, bytes: ORCHARD_ZSA_WORKFLOW_BLOCK_1_BYTES.as_slice(), - is_valid: true + expected_result: OrchardWorkflowBlockResult::Valid }, // Transfer OrchardWorkflowBlock { height: 2, bytes: ORCHARD_ZSA_WORKFLOW_BLOCK_2_BYTES.as_slice(), - is_valid: true + expected_result: OrchardWorkflowBlockResult::Valid }, // Burn: 7, Burn: 2 OrchardWorkflowBlock { height: 3, bytes: ORCHARD_ZSA_WORKFLOW_BLOCK_3_BYTES.as_slice(), - is_valid: true + expected_result: OrchardWorkflowBlockResult::Valid }, // Issue: finalize OrchardWorkflowBlock { height: 4, bytes: ORCHARD_ZSA_WORKFLOW_BLOCK_4_BYTES.as_slice(), - is_valid: true + expected_result: OrchardWorkflowBlockResult::Valid }, // Try to issue: 2000 OrchardWorkflowBlock { height: 5, bytes: ORCHARD_ZSA_WORKFLOW_BLOCK_5_BYTES.as_slice(), - is_valid: false + expected_result: OrchardWorkflowBlockResult::IssueFinalizedAssetError }, ]; }