From 71c1f6390860e8ccc1287615f1a49b35e8038ff6 Mon Sep 17 00:00:00 2001 From: Arya Date: Thu, 12 Dec 2024 21:34:21 -0500 Subject: [PATCH 1/5] Updates `coinbase_spend_restriction()` method to always return `OnlyShieldedOutputs` on Regtest. --- zebra-chain/src/block/arbitrary.rs | 2 +- zebra-chain/src/transaction.rs | 6 ++++-- zebra-consensus/src/transaction.rs | 4 +++- zebra-consensus/src/transaction/check.rs | 3 ++- zebra-consensus/src/transaction/tests.rs | 2 +- zebra-state/src/service/check/utxo.rs | 6 ++++-- 6 files changed, 15 insertions(+), 8 deletions(-) diff --git a/zebra-chain/src/block/arbitrary.rs b/zebra-chain/src/block/arbitrary.rs index 5a39afa2ee4..5a8a91d1700 100644 --- a/zebra-chain/src/block/arbitrary.rs +++ b/zebra-chain/src/block/arbitrary.rs @@ -568,7 +568,7 @@ where + Copy + 'static, { - let mut spend_restriction = transaction.coinbase_spend_restriction(height); + let mut spend_restriction = transaction.coinbase_spend_restriction(&Network::Mainnet, height); let mut new_inputs = Vec::new(); let mut spent_outputs = HashMap::new(); diff --git a/zebra-chain/src/transaction.rs b/zebra-chain/src/transaction.rs index 1c121130fcc..881fb4c5227 100644 --- a/zebra-chain/src/transaction.rs +++ b/zebra-chain/src/transaction.rs @@ -41,7 +41,7 @@ pub use unmined::{ use crate::{ amount::{Amount, Error as AmountError, NegativeAllowed, NonNegative}, block, orchard, - parameters::{ConsensusBranchId, NetworkUpgrade}, + parameters::{ConsensusBranchId, Network, NetworkUpgrade}, primitives::{ed25519, Bctv14Proof, Groth16Proof}, sapling, serialization::ZcashSerialize, @@ -303,9 +303,11 @@ impl Transaction { /// assuming it is mined at `spend_height`. pub fn coinbase_spend_restriction( &self, + network: &Network, spend_height: block::Height, ) -> CoinbaseSpendRestriction { - if self.outputs().is_empty() { + // TODO: Replace `is_regtest()` with a check for a field. + if self.outputs().is_empty() || network.is_regtest() { // we know this transaction must have shielded outputs, // because of other consensus rules OnlyShieldedOutputs { spend_height } diff --git a/zebra-consensus/src/transaction.rs b/zebra-consensus/src/transaction.rs index ef20881bbbf..864c668b144 100644 --- a/zebra-consensus/src/transaction.rs +++ b/zebra-consensus/src/transaction.rs @@ -451,7 +451,7 @@ where // WONTFIX: Return an error for Request::Block as well to replace this check in // the state once #2336 has been implemented? if req.is_mempool() { - Self::check_maturity_height(&req, &spent_utxos)?; + Self::check_maturity_height(&network, &req, &spent_utxos)?; } let cached_ffi_transaction = @@ -728,10 +728,12 @@ where /// mature and valid for the request height, or a [`TransactionError`] if the transaction /// spends transparent coinbase outputs that are immature and invalid for the request height. pub fn check_maturity_height( + network: &Network, request: &Request, spent_utxos: &HashMap, ) -> Result<(), TransactionError> { check::tx_transparent_coinbase_spends_maturity( + network, request.transaction(), request.height(), request.known_utxos(), diff --git a/zebra-consensus/src/transaction/check.rs b/zebra-consensus/src/transaction/check.rs index d3ddc460264..b7338bbdadd 100644 --- a/zebra-consensus/src/transaction/check.rs +++ b/zebra-consensus/src/transaction/check.rs @@ -476,6 +476,7 @@ fn validate_expiry_height_mined( /// Returns `Ok(())` if spent transparent coinbase outputs are /// valid for the block height, or a [`Err(TransactionError)`](TransactionError) pub fn tx_transparent_coinbase_spends_maturity( + network: &Network, tx: Arc, height: Height, block_new_outputs: Arc>, @@ -488,7 +489,7 @@ pub fn tx_transparent_coinbase_spends_maturity( .or_else(|| spent_utxos.get(&spend).cloned()) .expect("load_spent_utxos_fut.await should return an error if a utxo is missing"); - let spend_restriction = tx.coinbase_spend_restriction(height); + let spend_restriction = tx.coinbase_spend_restriction(network, height); zebra_state::check::transparent_coinbase_spend(spend, spend_restriction, &utxo)?; } diff --git a/zebra-consensus/src/transaction/tests.rs b/zebra-consensus/src/transaction/tests.rs index 8627a578c62..cca451d275e 100644 --- a/zebra-consensus/src/transaction/tests.rs +++ b/zebra-consensus/src/transaction/tests.rs @@ -739,7 +739,7 @@ async fn mempool_request_with_immature_spend_is_rejected() { transparent::Input::Coinbase { .. } => panic!("requires a non-coinbase transaction"), }; - let spend_restriction = tx.coinbase_spend_restriction(height); + let spend_restriction = tx.coinbase_spend_restriction(&Network::Mainnet, height); let coinbase_spend_height = Height(5); diff --git a/zebra-state/src/service/check/utxo.rs b/zebra-state/src/service/check/utxo.rs index 324efa3c035..ef6c2533c8c 100644 --- a/zebra-state/src/service/check/utxo.rs +++ b/zebra-state/src/service/check/utxo.rs @@ -72,8 +72,10 @@ pub fn transparent_spend( // We don't want to use UTXOs from invalid pending blocks, // so we check transparent coinbase maturity and shielding // using known valid UTXOs during non-finalized chain validation. - let spend_restriction = - transaction.coinbase_spend_restriction(semantically_verified.height); + let spend_restriction = transaction.coinbase_spend_restriction( + &finalized_state.network(), + semantically_verified.height, + ); transparent_coinbase_spend(spend, spend_restriction, utxo.as_ref())?; // We don't delete the UTXOs until the block is committed, From 539da483814d2e07936801cc079a0ad0979591c5 Mon Sep 17 00:00:00 2001 From: Arya Date: Thu, 12 Dec 2024 21:45:46 -0500 Subject: [PATCH 2/5] Adds a `should_allow_unshielded_coinbase_spends` field to testnet::Parameters --- zebra-chain/src/parameters/network/testnet.rs | 41 +++++++++++++++++++ zebra-chain/src/transaction.rs | 2 +- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/zebra-chain/src/parameters/network/testnet.rs b/zebra-chain/src/parameters/network/testnet.rs index 78f7a69a302..142399a8e72 100644 --- a/zebra-chain/src/parameters/network/testnet.rs +++ b/zebra-chain/src/parameters/network/testnet.rs @@ -232,6 +232,9 @@ pub struct ParametersBuilder { target_difficulty_limit: ExpandedDifficulty, /// A flag for disabling proof-of-work checks when Zebra is validating blocks disable_pow: bool, + /// Whether to allow transactions with transparent outputs that spend coinbase inputs, + /// similar to `fCoinbaseMustBeShielded` in zcashd. + should_allow_unshielded_coinbase_spends: bool, /// The pre-Blossom halving interval for this network pre_blossom_halving_interval: HeightDiff, /// The post-Blossom halving interval for this network @@ -271,6 +274,7 @@ impl Default for ParametersBuilder { should_lock_funding_stream_address_period: false, pre_blossom_halving_interval: PRE_BLOSSOM_HALVING_INTERVAL, post_blossom_halving_interval: POST_BLOSSOM_HALVING_INTERVAL, + should_allow_unshielded_coinbase_spends: false, } } } @@ -439,6 +443,15 @@ impl ParametersBuilder { self } + /// Sets the `disable_pow` flag to be used in the [`Parameters`] being built. + pub fn with_unshielded_coinbase_spends( + mut self, + should_allow_unshielded_coinbase_spends: bool, + ) -> Self { + self.should_allow_unshielded_coinbase_spends = should_allow_unshielded_coinbase_spends; + self + } + /// Sets the pre and post Blosssom halving intervals to be used in the [`Parameters`] being built. pub fn with_halving_interval(mut self, pre_blossom_halving_interval: HeightDiff) -> Self { if self.should_lock_funding_stream_address_period { @@ -464,6 +477,7 @@ impl ParametersBuilder { should_lock_funding_stream_address_period: _, target_difficulty_limit, disable_pow, + should_allow_unshielded_coinbase_spends, pre_blossom_halving_interval, post_blossom_halving_interval, } = self; @@ -478,6 +492,7 @@ impl ParametersBuilder { post_nu6_funding_streams, target_difficulty_limit, disable_pow, + should_allow_unshielded_coinbase_spends, pre_blossom_halving_interval, post_blossom_halving_interval, } @@ -516,6 +531,7 @@ impl ParametersBuilder { should_lock_funding_stream_address_period: _, target_difficulty_limit, disable_pow, + should_allow_unshielded_coinbase_spends, pre_blossom_halving_interval, post_blossom_halving_interval, } = Self::default(); @@ -528,6 +544,8 @@ impl ParametersBuilder { && self.post_nu6_funding_streams == post_nu6_funding_streams && self.target_difficulty_limit == target_difficulty_limit && self.disable_pow == disable_pow + && self.should_allow_unshielded_coinbase_spends + == should_allow_unshielded_coinbase_spends && self.pre_blossom_halving_interval == pre_blossom_halving_interval && self.post_blossom_halving_interval == post_blossom_halving_interval } @@ -560,6 +578,9 @@ pub struct Parameters { target_difficulty_limit: ExpandedDifficulty, /// A flag for disabling proof-of-work checks when Zebra is validating blocks disable_pow: bool, + /// Whether to allow transactions with transparent outputs that spend coinbase inputs, + /// similar to `fCoinbaseMustBeShielded` in zcashd. + should_allow_unshielded_coinbase_spends: bool, /// Pre-Blossom halving interval for this network pre_blossom_halving_interval: HeightDiff, /// Post-Blossom halving interval for this network @@ -597,6 +618,7 @@ impl Parameters { // This value is chosen to match zcashd, see: .with_target_difficulty_limit(U256::from_big_endian(&[0x0f; 32])) .with_disable_pow(true) + .with_unshielded_coinbase_spends(true) .with_slow_start_interval(Height::MIN) // Removes default Testnet activation heights if not configured, // most network upgrades are disabled by default for Regtest in zcashd @@ -645,6 +667,7 @@ impl Parameters { post_nu6_funding_streams, target_difficulty_limit, disable_pow, + should_allow_unshielded_coinbase_spends, pre_blossom_halving_interval, post_blossom_halving_interval, } = Self::new_regtest(None, None); @@ -657,6 +680,8 @@ impl Parameters { && self.post_nu6_funding_streams == post_nu6_funding_streams && self.target_difficulty_limit == target_difficulty_limit && self.disable_pow == disable_pow + && self.should_allow_unshielded_coinbase_spends + == should_allow_unshielded_coinbase_spends && self.pre_blossom_halving_interval == pre_blossom_halving_interval && self.post_blossom_halving_interval == post_blossom_halving_interval } @@ -711,6 +736,12 @@ impl Parameters { self.disable_pow } + /// Returns true if this network should allow transactions with transparent outputs + /// that spend coinbase inputs + pub fn should_allow_unshielded_coinbase_spends(&self) -> bool { + self.should_allow_unshielded_coinbase_spends + } + /// Returns the pre-Blossom halving interval for this network pub fn pre_blossom_halving_interval(&self) -> HeightDiff { self.pre_blossom_halving_interval @@ -786,4 +817,14 @@ impl Network { self.post_nu6_funding_streams() } } + + /// Returns true if this network should allow transactions with transparent outputs + /// that spend coinbase inputs + pub fn should_allow_unshielded_coinbase_spends(&self) -> bool { + if let Self::Testnet(params) = self { + params.should_allow_unshielded_coinbase_spends() + } else { + false + } + } } diff --git a/zebra-chain/src/transaction.rs b/zebra-chain/src/transaction.rs index 881fb4c5227..ffbbe8ee820 100644 --- a/zebra-chain/src/transaction.rs +++ b/zebra-chain/src/transaction.rs @@ -307,7 +307,7 @@ impl Transaction { spend_height: block::Height, ) -> CoinbaseSpendRestriction { // TODO: Replace `is_regtest()` with a check for a field. - if self.outputs().is_empty() || network.is_regtest() { + if self.outputs().is_empty() || network.should_allow_unshielded_coinbase_spends() { // we know this transaction must have shielded outputs, // because of other consensus rules OnlyShieldedOutputs { spend_height } From 2ba1e3f8955e6f2d353eace9cc34507ac2e9c584 Mon Sep 17 00:00:00 2001 From: Arya Date: Thu, 12 Dec 2024 21:57:11 -0500 Subject: [PATCH 3/5] Adds a test --- zebra-consensus/src/transaction/tests.rs | 106 ++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/zebra-consensus/src/transaction/tests.rs b/zebra-consensus/src/transaction/tests.rs index cca451d275e..991cbb8cfa8 100644 --- a/zebra-consensus/src/transaction/tests.rs +++ b/zebra-consensus/src/transaction/tests.rs @@ -26,7 +26,7 @@ use zebra_chain::{ }, zip317, Hash, HashType, JoinSplitData, LockTime, Transaction, }, - transparent::{self, CoinbaseData}, + transparent::{self, CoinbaseData, CoinbaseSpendRestriction}, }; use zebra_node_services::mempool; @@ -807,6 +807,110 @@ async fn mempool_request_with_immature_spend_is_rejected() { ); } +/// Tests that calls to the transaction verifier with a mempool request that spends +/// mature coinbase outputs to transparent outputs will return Ok() on Regtest. +#[tokio::test] +async fn mempool_request_with_transparent_coinbase_spend_is_accepted_on_regtest() { + let _init_guard = zebra_test::init(); + + let network = Network::new_regtest(None, Some(1_000)); + let mut state: MockService<_, _, _, _> = MockService::build().for_prop_tests(); + let verifier = Verifier::new_for_tests(&network, state.clone()); + + let height = NetworkUpgrade::Nu6 + .activation_height(&network) + .expect("Canopy activation height is specified"); + let fund_height = (height - 1).expect("fake source fund block height is too small"); + let (input, output, known_utxos) = mock_transparent_transfer( + fund_height, + true, + 0, + Amount::try_from(10001).expect("invalid value"), + ); + + // Create a non-coinbase V4 tx with the last valid expiry height. + let tx = Transaction::V5 { + network_upgrade: NetworkUpgrade::Nu6, + inputs: vec![input], + outputs: vec![output], + lock_time: LockTime::min_lock_time_timestamp(), + expiry_height: height, + sapling_shielded_data: None, + orchard_shielded_data: None, + }; + + let input_outpoint = match tx.inputs()[0] { + transparent::Input::PrevOut { outpoint, .. } => outpoint, + transparent::Input::Coinbase { .. } => panic!("requires a non-coinbase transaction"), + }; + + let spend_restriction = tx.coinbase_spend_restriction(&network, height); + + assert_eq!( + spend_restriction, + CoinbaseSpendRestriction::OnlyShieldedOutputs { + spend_height: height + } + ); + + let coinbase_spend_height = Height(5); + + let utxo = known_utxos + .get(&input_outpoint) + .map(|utxo| { + let mut utxo = utxo.utxo.clone(); + utxo.height = coinbase_spend_height; + utxo.from_coinbase = true; + utxo + }) + .expect("known_utxos should contain the outpoint"); + + zebra_state::check::transparent_coinbase_spend(input_outpoint, spend_restriction, &utxo) + .expect("check should pass"); + + tokio::spawn(async move { + state + .expect_request(zebra_state::Request::BestChainNextMedianTimePast) + .await + .expect("verifier should call mock state service with correct request") + .respond(zebra_state::Response::BestChainNextMedianTimePast( + DateTime32::MAX, + )); + + state + .expect_request(zebra_state::Request::UnspentBestChainUtxo(input_outpoint)) + .await + .expect("verifier should call mock state service with correct request") + .respond(zebra_state::Response::UnspentBestChainUtxo( + known_utxos.get(&input_outpoint).map(|utxo| { + let mut utxo = utxo.utxo.clone(); + utxo.height = coinbase_spend_height; + utxo.from_coinbase = true; + utxo + }), + )); + + state + .expect_request_that(|req| { + matches!( + req, + zebra_state::Request::CheckBestChainTipNullifiersAndAnchors(_) + ) + }) + .await + .expect("verifier should call mock state service with correct request") + .respond(zebra_state::Response::ValidBestChainTipNullifiersAndAnchors); + }); + + verifier + .oneshot(Request::Mempool { + transaction: tx.into(), + height, + }) + .await + .expect("verification of transaction with mature spend to transparent outputs should pass"); +} + /// Tests that errors from the read state service are correctly converted into /// transaction verifier errors. #[tokio::test] From 1b69b498e7e806a8b9a5d98a4acdf17134980c7f Mon Sep 17 00:00:00 2001 From: Arya Date: Thu, 19 Dec 2024 14:57:46 -0500 Subject: [PATCH 4/5] Apply suggestions from code review Co-authored-by: Marek --- zebra-chain/src/parameters/network/testnet.rs | 8 ++++---- zebra-chain/src/transaction.rs | 1 - zebra-consensus/src/transaction/tests.rs | 18 ++++-------------- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/zebra-chain/src/parameters/network/testnet.rs b/zebra-chain/src/parameters/network/testnet.rs index 142399a8e72..1f77e95e750 100644 --- a/zebra-chain/src/parameters/network/testnet.rs +++ b/zebra-chain/src/parameters/network/testnet.rs @@ -232,7 +232,7 @@ pub struct ParametersBuilder { target_difficulty_limit: ExpandedDifficulty, /// A flag for disabling proof-of-work checks when Zebra is validating blocks disable_pow: bool, - /// Whether to allow transactions with transparent outputs that spend coinbase inputs, + /// Whether to allow transactions with transparent outputs to spend coinbase outputs, /// similar to `fCoinbaseMustBeShielded` in zcashd. should_allow_unshielded_coinbase_spends: bool, /// The pre-Blossom halving interval for this network @@ -578,7 +578,7 @@ pub struct Parameters { target_difficulty_limit: ExpandedDifficulty, /// A flag for disabling proof-of-work checks when Zebra is validating blocks disable_pow: bool, - /// Whether to allow transactions with transparent outputs that spend coinbase inputs, + /// Whether to allow transactions with transparent outputs to spend coinbase outputs, /// similar to `fCoinbaseMustBeShielded` in zcashd. should_allow_unshielded_coinbase_spends: bool, /// Pre-Blossom halving interval for this network @@ -737,7 +737,7 @@ impl Parameters { } /// Returns true if this network should allow transactions with transparent outputs - /// that spend coinbase inputs + /// that spend coinbase outputs. pub fn should_allow_unshielded_coinbase_spends(&self) -> bool { self.should_allow_unshielded_coinbase_spends } @@ -819,7 +819,7 @@ impl Network { } /// Returns true if this network should allow transactions with transparent outputs - /// that spend coinbase inputs + /// that spend coinbase outputs. pub fn should_allow_unshielded_coinbase_spends(&self) -> bool { if let Self::Testnet(params) = self { params.should_allow_unshielded_coinbase_spends() diff --git a/zebra-chain/src/transaction.rs b/zebra-chain/src/transaction.rs index ffbbe8ee820..9d3135d239c 100644 --- a/zebra-chain/src/transaction.rs +++ b/zebra-chain/src/transaction.rs @@ -306,7 +306,6 @@ impl Transaction { network: &Network, spend_height: block::Height, ) -> CoinbaseSpendRestriction { - // TODO: Replace `is_regtest()` with a check for a field. if self.outputs().is_empty() || network.should_allow_unshielded_coinbase_spends() { // we know this transaction must have shielded outputs, // because of other consensus rules diff --git a/zebra-consensus/src/transaction/tests.rs b/zebra-consensus/src/transaction/tests.rs index 991cbb8cfa8..d268358282e 100644 --- a/zebra-consensus/src/transaction/tests.rs +++ b/zebra-consensus/src/transaction/tests.rs @@ -814,12 +814,12 @@ async fn mempool_request_with_transparent_coinbase_spend_is_accepted_on_regtest( let _init_guard = zebra_test::init(); let network = Network::new_regtest(None, Some(1_000)); - let mut state: MockService<_, _, _, _> = MockService::build().for_prop_tests(); + let mut state: MockService<_, _, _, _> = MockService::build().for_unit_tests(); let verifier = Verifier::new_for_tests(&network, state.clone()); let height = NetworkUpgrade::Nu6 .activation_height(&network) - .expect("Canopy activation height is specified"); + .expect("NU6 activation height is specified"); let fund_height = (height - 1).expect("fake source fund block height is too small"); let (input, output, known_utxos) = mock_transparent_transfer( fund_height, @@ -828,7 +828,7 @@ async fn mempool_request_with_transparent_coinbase_spend_is_accepted_on_regtest( Amount::try_from(10001).expect("invalid value"), ); - // Create a non-coinbase V4 tx with the last valid expiry height. + // Create a non-coinbase V5 tx with the last valid expiry height. let tx = Transaction::V5 { network_upgrade: NetworkUpgrade::Nu6, inputs: vec![input], @@ -872,7 +872,6 @@ async fn mempool_request_with_transparent_coinbase_spend_is_accepted_on_regtest( state .expect_request(zebra_state::Request::BestChainNextMedianTimePast) .await - .expect("verifier should call mock state service with correct request") .respond(zebra_state::Response::BestChainNextMedianTimePast( DateTime32::MAX, )); @@ -880,15 +879,7 @@ async fn mempool_request_with_transparent_coinbase_spend_is_accepted_on_regtest( state .expect_request(zebra_state::Request::UnspentBestChainUtxo(input_outpoint)) .await - .expect("verifier should call mock state service with correct request") - .respond(zebra_state::Response::UnspentBestChainUtxo( - known_utxos.get(&input_outpoint).map(|utxo| { - let mut utxo = utxo.utxo.clone(); - utxo.height = coinbase_spend_height; - utxo.from_coinbase = true; - utxo - }), - )); + .respond(zebra_state::Response::UnspentBestChainUtxo(Some(utxo))); state .expect_request_that(|req| { @@ -898,7 +889,6 @@ async fn mempool_request_with_transparent_coinbase_spend_is_accepted_on_regtest( ) }) .await - .expect("verifier should call mock state service with correct request") .respond(zebra_state::Response::ValidBestChainTipNullifiersAndAnchors); }); From 166261464f24f3d1ac70bea1d6ceea291daa56f6 Mon Sep 17 00:00:00 2001 From: ar Date: Thu, 19 Dec 2024 15:25:43 -0500 Subject: [PATCH 5/5] Renames CoinbaseSpendRestriction variants and updates their documentation. Updates a comment. --- zebra-chain/src/block/arbitrary.rs | 3 ++- zebra-chain/src/transaction.rs | 8 ++++---- zebra-chain/src/transparent/utxo.rs | 10 +++++++--- zebra-consensus/src/transaction/tests.rs | 2 +- zebra-state/src/service/check/tests/utxo.rs | 6 +++--- zebra-state/src/service/check/utxo.rs | 4 ++-- 6 files changed, 19 insertions(+), 14 deletions(-) diff --git a/zebra-chain/src/block/arbitrary.rs b/zebra-chain/src/block/arbitrary.rs index 5a8a91d1700..4961c873b3e 100644 --- a/zebra-chain/src/block/arbitrary.rs +++ b/zebra-chain/src/block/arbitrary.rs @@ -650,7 +650,8 @@ where + 'static, { let has_shielded_outputs = transaction.has_shielded_outputs(); - let delete_transparent_outputs = CoinbaseSpendRestriction::OnlyShieldedOutputs { spend_height }; + let delete_transparent_outputs = + CoinbaseSpendRestriction::CheckCoinbaseMaturity { spend_height }; let mut attempts: usize = 0; // choose an arbitrary spendable UTXO, in hash set order diff --git a/zebra-chain/src/transaction.rs b/zebra-chain/src/transaction.rs index 9d3135d239c..d29eadff8cf 100644 --- a/zebra-chain/src/transaction.rs +++ b/zebra-chain/src/transaction.rs @@ -307,11 +307,11 @@ impl Transaction { spend_height: block::Height, ) -> CoinbaseSpendRestriction { if self.outputs().is_empty() || network.should_allow_unshielded_coinbase_spends() { - // we know this transaction must have shielded outputs, - // because of other consensus rules - OnlyShieldedOutputs { spend_height } + // we know this transaction must have shielded outputs if it has no + // transparent outputs, because of other consensus rules. + CheckCoinbaseMaturity { spend_height } } else { - SomeTransparentOutputs + DisallowCoinbaseSpend } } diff --git a/zebra-chain/src/transparent/utxo.rs b/zebra-chain/src/transparent/utxo.rs index 0158165193a..1b5f49bcd89 100644 --- a/zebra-chain/src/transparent/utxo.rs +++ b/zebra-chain/src/transparent/utxo.rs @@ -126,10 +126,14 @@ impl OrderedUtxo { )] pub enum CoinbaseSpendRestriction { /// The UTXO is spent in a transaction with one or more transparent outputs - SomeTransparentOutputs, + /// on a network where coinbase outputs must not be spent by transactions + /// with transparent outputs. + DisallowCoinbaseSpend, - /// The UTXO is spent in a transaction which only has shielded outputs - OnlyShieldedOutputs { + /// The UTXO is spent in a transaction which only has shielded outputs, or + /// transactions spending coinbase outputs may have transparent outputs on + /// this network. + CheckCoinbaseMaturity { /// The height at which the UTXO is spent spend_height: block::Height, }, diff --git a/zebra-consensus/src/transaction/tests.rs b/zebra-consensus/src/transaction/tests.rs index d268358282e..bc0d9ec7898 100644 --- a/zebra-consensus/src/transaction/tests.rs +++ b/zebra-consensus/src/transaction/tests.rs @@ -848,7 +848,7 @@ async fn mempool_request_with_transparent_coinbase_spend_is_accepted_on_regtest( assert_eq!( spend_restriction, - CoinbaseSpendRestriction::OnlyShieldedOutputs { + CoinbaseSpendRestriction::CheckCoinbaseMaturity { spend_height: height } ); diff --git a/zebra-state/src/service/check/tests/utxo.rs b/zebra-state/src/service/check/tests/utxo.rs index acdc2d399a7..57d087c552d 100644 --- a/zebra-state/src/service/check/tests/utxo.rs +++ b/zebra-state/src/service/check/tests/utxo.rs @@ -48,7 +48,7 @@ fn accept_shielded_mature_coinbase_utxo_spend() { let ordered_utxo = transparent::OrderedUtxo::new(output, created_height, 0); let min_spend_height = Height(created_height.0 + MIN_TRANSPARENT_COINBASE_MATURITY); - let spend_restriction = transparent::CoinbaseSpendRestriction::OnlyShieldedOutputs { + let spend_restriction = transparent::CoinbaseSpendRestriction::CheckCoinbaseMaturity { spend_height: min_spend_height, }; @@ -78,7 +78,7 @@ fn reject_unshielded_coinbase_utxo_spend() { }; let ordered_utxo = transparent::OrderedUtxo::new(output, created_height, 0); - let spend_restriction = transparent::CoinbaseSpendRestriction::SomeTransparentOutputs; + let spend_restriction = transparent::CoinbaseSpendRestriction::DisallowCoinbaseSpend; let result = check::utxo::transparent_coinbase_spend(outpoint, spend_restriction, ordered_utxo.as_ref()); @@ -104,7 +104,7 @@ fn reject_immature_coinbase_utxo_spend() { let min_spend_height = Height(created_height.0 + MIN_TRANSPARENT_COINBASE_MATURITY); let spend_height = Height(min_spend_height.0 - 1); let spend_restriction = - transparent::CoinbaseSpendRestriction::OnlyShieldedOutputs { spend_height }; + transparent::CoinbaseSpendRestriction::CheckCoinbaseMaturity { spend_height }; let result = check::utxo::transparent_coinbase_spend(outpoint, spend_restriction, ordered_utxo.as_ref()); diff --git a/zebra-state/src/service/check/utxo.rs b/zebra-state/src/service/check/utxo.rs index ef6c2533c8c..df3981ec0b8 100644 --- a/zebra-state/src/service/check/utxo.rs +++ b/zebra-state/src/service/check/utxo.rs @@ -197,7 +197,7 @@ pub fn transparent_coinbase_spend( } match spend_restriction { - OnlyShieldedOutputs { spend_height } => { + CheckCoinbaseMaturity { spend_height } => { let min_spend_height = utxo.height + MIN_TRANSPARENT_COINBASE_MATURITY.into(); let min_spend_height = min_spend_height.expect("valid UTXOs have coinbase heights far below Height::MAX"); @@ -212,7 +212,7 @@ pub fn transparent_coinbase_spend( }) } } - SomeTransparentOutputs => Err(UnshieldedTransparentCoinbaseSpend { outpoint }), + DisallowCoinbaseSpend => Err(UnshieldedTransparentCoinbaseSpend { outpoint }), } }