validate orchard flags in v5 (#3035)

This commit is contained in:
Alfredo Garcia 2021-11-08 18:45:54 -03:00 committed by GitHub
parent c393b9a488
commit f7c1907fb6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 76 additions and 0 deletions

View File

@ -266,6 +266,21 @@ impl Transaction {
.contains(orchard::Flags::ENABLE_OUTPUTS))
}
/// Does this transaction has at least one flag when we have at least one orchard action?
///
/// [NU5 onward] If effectiveVersion >= 5 and nActionsOrchard > 0, then at least one
/// of enableSpendsOrchard and enableOutputsOrchard MUST be 1.
///
/// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
pub fn has_enough_orchard_flags(&self) -> bool {
if self.version() < 5 || self.orchard_actions().count() == 0 {
return true;
}
self.orchard_flags()
.unwrap_or_else(orchard::Flags::empty)
.intersects(orchard::Flags::ENABLE_SPENDS | orchard::Flags::ENABLE_OUTPUTS)
}
/// Returns the [`CoinbaseSpendRestriction`] for this transaction,
/// assuming it is mined at `spend_height`.
pub fn coinbase_spend_restriction(

View File

@ -116,6 +116,9 @@ pub enum TransactionError {
#[error("orchard double-spend: duplicate nullifier: {_0:?}")]
DuplicateOrchardNullifier(orchard::Nullifier),
#[error("must have at least one active orchard flag")]
NotEnoughFlags,
}
impl From<BoxError> for TransactionError {

View File

@ -265,6 +265,7 @@ where
// Do basic checks first
check::has_inputs_and_outputs(&tx)?;
check::has_enough_orchard_flags(&tx)?;
if req.is_mempool() && tx.has_any_coinbase_inputs() {
return Err(TransactionError::CoinbaseInMempool);

View File

@ -40,6 +40,20 @@ pub fn has_inputs_and_outputs(tx: &Transaction) -> Result<(), TransactionError>
}
}
/// Checks that the transaction has enough orchard flags.
///
/// For `Transaction::V5` only:
/// * If `orchard_actions_count` > 0 then at least one of
/// `ENABLE_SPENDS|ENABLE_OUTPUTS` must be active.
///
/// https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus
pub fn has_enough_orchard_flags(tx: &Transaction) -> Result<(), TransactionError> {
if !tx.has_enough_orchard_flags() {
return Err(TransactionError::NotEnoughFlags);
}
Ok(())
}
/// Check that a coinbase transaction has no PrevOut inputs, JoinSplits, or spends.
///
/// A coinbase transaction MUST NOT have any transparent inputs, JoinSplit descriptions,

View File

@ -114,6 +114,49 @@ fn fake_v5_transaction_with_orchard_actions_has_inputs_and_outputs() {
assert!(check::has_inputs_and_outputs(&transaction).is_ok());
}
#[test]
fn fake_v5_transaction_with_orchard_actions_has_flags() {
// Find a transaction with no inputs or outputs to use as base
let mut transaction = fake_v5_transactions_for_network(
Network::Mainnet,
zebra_test::vectors::MAINNET_BLOCKS.iter(),
)
.rev()
.find(|transaction| {
transaction.inputs().is_empty()
&& transaction.outputs().is_empty()
&& transaction.sapling_spends_per_anchor().next().is_none()
&& transaction.sapling_outputs().next().is_none()
&& transaction.joinsplit_count() == 0
})
.expect("At least one fake V5 transaction with no inputs and no outputs");
// Insert fake Orchard shielded data to the transaction, which has at least one action (this is
// guaranteed structurally by `orchard::ShieldedData`)
insert_fake_orchard_shielded_data(&mut transaction);
// The check will fail if the transaction has no flags
assert_eq!(
check::has_enough_orchard_flags(&transaction),
Err(TransactionError::NotEnoughFlags)
);
// If we add ENABLE_SPENDS flag it will pass.
let shielded_data = insert_fake_orchard_shielded_data(&mut transaction);
shielded_data.flags = orchard::Flags::ENABLE_SPENDS;
assert!(check::has_enough_orchard_flags(&transaction).is_ok());
// If we add ENABLE_OUTPUTS flag instead, it will pass.
let shielded_data = insert_fake_orchard_shielded_data(&mut transaction);
shielded_data.flags = orchard::Flags::ENABLE_OUTPUTS;
assert!(check::has_enough_orchard_flags(&transaction).is_ok());
// If we add BOTH ENABLE_SPENDS and ENABLE_OUTPUTS flags it will pass.
let shielded_data = insert_fake_orchard_shielded_data(&mut transaction);
shielded_data.flags = orchard::Flags::ENABLE_SPENDS | orchard::Flags::ENABLE_OUTPUTS;
assert!(check::has_enough_orchard_flags(&transaction).is_ok());
}
#[test]
fn v5_transaction_with_no_inputs_fails_validation() {
let transaction = fake_v5_transactions_for_network(