diff --git a/zebra-consensus/src/error.rs b/zebra-consensus/src/error.rs index 70c002c32..2f5458d58 100644 --- a/zebra-consensus/src/error.rs +++ b/zebra-consensus/src/error.rs @@ -28,8 +28,14 @@ pub enum TransactionError { #[error("coinbase input found in non-coinbase transaction")] CoinbaseInputFound, - #[error("coinbase transaction MUST NOT have any JoinSplit descriptions or Spend descriptions")] - CoinbaseHasJoinSplitOrSpend, + #[error("coinbase transaction MUST NOT have any JoinSplit descriptions")] + CoinbaseHasJoinSplit, + + #[error("coinbase transaction MUST NOT have any Spend descriptions")] + CoinbaseHasSpend, + + #[error("coinbase transaction MUST NOT have any Output descriptions pre-Heartwood")] + CoinbaseHasOutputPreHeartwood, #[error("coinbase transaction failed subsidy validation")] Subsidy(#[from] SubsidyError), diff --git a/zebra-consensus/src/transaction.rs b/zebra-consensus/src/transaction.rs index 3f0986b67..b3fe435bb 100644 --- a/zebra-consensus/src/transaction.rs +++ b/zebra-consensus/src/transaction.rs @@ -116,7 +116,7 @@ where #[allow(clippy::if_same_then_else)] // delete when filled in if tx.is_coinbase() { // do something special for coinbase transactions - check::coinbase_tx_does_not_spend_shielded(&tx)?; + check::coinbase_tx_no_joinsplit_or_spend(&tx)?; } else { // otherwise, check no coinbase inputs // feed all of the inputs to the script verifier diff --git a/zebra-consensus/src/transaction/check.rs b/zebra-consensus/src/transaction/check.rs index aa3b13bc1..9761d4454 100644 --- a/zebra-consensus/src/transaction/check.rs +++ b/zebra-consensus/src/transaction/check.rs @@ -1,3 +1,7 @@ +//! Transaction checks. +//! +//! Code in this file can freely assume that no pre-V4 transactions are present. + use std::convert::TryFrom; use zebra_chain::{ @@ -86,22 +90,30 @@ pub fn shielded_balances_match( /// Check that a coinbase tx does not have any JoinSplit or Spend descriptions. /// -/// https://zips.z.cash/protocol/canopy.pdf#consensusfrombitcoin -pub fn coinbase_tx_does_not_spend_shielded(tx: &Transaction) -> Result<(), TransactionError> { - match tx { - Transaction::V4 { - joinsplit_data: Some(joinsplit_data), - shielded_data: Some(shielded_data), - .. - } => { - if !tx.is_coinbase() - || (joinsplit_data.joinsplits().count() == 0 && shielded_data.spends().count() == 0) - { - Ok(()) - } else { - Err(TransactionError::CoinbaseHasJoinSplitOrSpend) +/// https://zips.z.cash/protocol/canopy.pdf#txnencodingandconsensus +pub fn coinbase_tx_no_joinsplit_or_spend(tx: &Transaction) -> Result<(), TransactionError> { + if tx.is_coinbase() { + match tx { + // Check if there is any JoinSplitData. + Transaction::V4 { + joinsplit_data: Some(_), + .. + } => Err(TransactionError::CoinbaseHasJoinSplit), + + // The ShieldedData contains both Spends and Outputs, and Outputs + // are allowed post-Heartwood, so we have to count Spends. + Transaction::V4 { + shielded_data: Some(shielded_data), + .. + } if shielded_data.spends().count() > 0 => Err(TransactionError::CoinbaseHasSpend), + + Transaction::V4 { .. } => Ok(()), + + Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => { + unreachable!("tx version is checked first") } } - _ => Err(TransactionError::WrongVersion), + } else { + Ok(()) } }