Update `transaction::check::coinbase_tx_no_joinsplit_or_spend` to validate V5 coinbase transactions with Orchard shielded data (#2236)
* Add a `Transaction::orchard_shielded_data` getter Allows accessing the Orchard shielded data if it is present in the transaction, regardless of the transaction version. * Refactor `orchard_nullifiers` to use new getter Allows making the method more concise. * Add `CoinbaseHasEnableSpendsOrchard` error variant Used when the validation rule is not met. * Implement `enableSpendsOrchard` in coinbase check The flag must not be set for the coinbase transaction. * Refactor `Transaction::orchard_*` getters Use the fact that `Option<T>` implements `Iterator<T>` to simplify the code and remove the need for boxing the iterators. Co-authored-by: teor <teor@riseup.net>
This commit is contained in:
parent
a9fe0d9d3e
commit
9416b5d5cd
|
@ -395,47 +395,39 @@ impl Transaction {
|
||||||
|
|
||||||
// orchard
|
// orchard
|
||||||
|
|
||||||
/// Iterate over the [`orchard::Action`]s in this transaction, if there are any.
|
/// Access the [`orchard::ShieldedData`] in this transaction, if there are any,
|
||||||
pub fn orchard_actions(&self) -> Box<dyn Iterator<Item = &orchard::Action> + '_> {
|
/// regardless of version.
|
||||||
|
pub fn orchard_shielded_data(&self) -> Option<&orchard::ShieldedData> {
|
||||||
match self {
|
match self {
|
||||||
// Actions
|
// Maybe Orchard shielded data
|
||||||
Transaction::V5 {
|
Transaction::V5 {
|
||||||
orchard_shielded_data: Some(orchard_shielded_data),
|
orchard_shielded_data,
|
||||||
..
|
..
|
||||||
} => Box::new(orchard_shielded_data.actions()),
|
} => orchard_shielded_data.as_ref(),
|
||||||
|
|
||||||
// No Actions
|
// No Orchard shielded data
|
||||||
Transaction::V1 { .. }
|
Transaction::V1 { .. }
|
||||||
| Transaction::V2 { .. }
|
| Transaction::V2 { .. }
|
||||||
| Transaction::V3 { .. }
|
| Transaction::V3 { .. }
|
||||||
| Transaction::V4 { .. }
|
| Transaction::V4 { .. } => None,
|
||||||
| Transaction::V5 {
|
|
||||||
orchard_shielded_data: None,
|
|
||||||
..
|
|
||||||
} => Box::new(std::iter::empty()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access the [`orchard::Nullifier`]s in this transaction, regardless of version.
|
/// Iterate over the [`orchard::Action`]s in this transaction, if there are any,
|
||||||
pub fn orchard_nullifiers(&self) -> Box<dyn Iterator<Item = &orchard::Nullifier> + '_> {
|
/// regardless of version.
|
||||||
// This function returns a boxed iterator because the different
|
pub fn orchard_actions(&self) -> impl Iterator<Item = &orchard::Action> {
|
||||||
// transaction variants can have different iterator types
|
self.orchard_shielded_data()
|
||||||
match self {
|
.into_iter()
|
||||||
// Actions
|
.map(orchard::ShieldedData::actions)
|
||||||
Transaction::V5 {
|
.flatten()
|
||||||
orchard_shielded_data: Some(orchard_shielded_data),
|
}
|
||||||
..
|
|
||||||
} => Box::new(orchard_shielded_data.nullifiers()),
|
|
||||||
|
|
||||||
// No Actions
|
/// Access the [`orchard::Nullifier`]s in this transaction, if there are any,
|
||||||
Transaction::V1 { .. }
|
/// regardless of version.
|
||||||
| Transaction::V2 { .. }
|
pub fn orchard_nullifiers(&self) -> impl Iterator<Item = &orchard::Nullifier> {
|
||||||
| Transaction::V3 { .. }
|
self.orchard_shielded_data()
|
||||||
| Transaction::V4 { .. }
|
.into_iter()
|
||||||
| Transaction::V5 {
|
.map(orchard::ShieldedData::nullifiers)
|
||||||
orchard_shielded_data: None,
|
.flatten()
|
||||||
..
|
|
||||||
} => Box::new(std::iter::empty()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,9 @@ pub enum TransactionError {
|
||||||
#[error("coinbase transaction MUST NOT have any Output descriptions pre-Heartwood")]
|
#[error("coinbase transaction MUST NOT have any Output descriptions pre-Heartwood")]
|
||||||
CoinbaseHasOutputPreHeartwood,
|
CoinbaseHasOutputPreHeartwood,
|
||||||
|
|
||||||
|
#[error("coinbase transaction MUST NOT have the EnableSpendsOrchard flag set")]
|
||||||
|
CoinbaseHasEnableSpendsOrchard,
|
||||||
|
|
||||||
#[error("coinbase transaction failed subsidy validation")]
|
#[error("coinbase transaction failed subsidy validation")]
|
||||||
Subsidy(#[from] SubsidyError),
|
Subsidy(#[from] SubsidyError),
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
//! Code in this file can freely assume that no pre-V4 transactions are present.
|
//! Code in this file can freely assume that no pre-V4 transactions are present.
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
|
orchard::Flags,
|
||||||
sapling::{AnchorVariant, Output, PerSpendAnchor, ShieldedData, Spend},
|
sapling::{AnchorVariant, Output, PerSpendAnchor, ShieldedData, Spend},
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
};
|
};
|
||||||
|
@ -84,8 +85,11 @@ pub fn coinbase_tx_no_prevout_joinsplit_spend(tx: &Transaction) -> Result<(), Tr
|
||||||
return Err(TransactionError::CoinbaseHasSpend);
|
return Err(TransactionError::CoinbaseHasSpend);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Orchard validation (#1980)
|
if let Some(orchard_shielded_data) = tx.orchard_shielded_data() {
|
||||||
// In a version 5 coinbase transaction, the enableSpendsOrchard flag MUST be 0.
|
if orchard_shielded_data.flags.contains(Flags::ENABLE_SPENDS) {
|
||||||
|
return Err(TransactionError::CoinbaseHasEnableSpendsOrchard);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Reference in New Issue