validate orchard flags in v5 (#3035)
This commit is contained in:
parent
c393b9a488
commit
f7c1907fb6
|
@ -266,6 +266,21 @@ impl Transaction {
|
||||||
.contains(orchard::Flags::ENABLE_OUTPUTS))
|
.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,
|
/// Returns the [`CoinbaseSpendRestriction`] for this transaction,
|
||||||
/// assuming it is mined at `spend_height`.
|
/// assuming it is mined at `spend_height`.
|
||||||
pub fn coinbase_spend_restriction(
|
pub fn coinbase_spend_restriction(
|
||||||
|
|
|
@ -116,6 +116,9 @@ pub enum TransactionError {
|
||||||
|
|
||||||
#[error("orchard double-spend: duplicate nullifier: {_0:?}")]
|
#[error("orchard double-spend: duplicate nullifier: {_0:?}")]
|
||||||
DuplicateOrchardNullifier(orchard::Nullifier),
|
DuplicateOrchardNullifier(orchard::Nullifier),
|
||||||
|
|
||||||
|
#[error("must have at least one active orchard flag")]
|
||||||
|
NotEnoughFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BoxError> for TransactionError {
|
impl From<BoxError> for TransactionError {
|
||||||
|
|
|
@ -265,6 +265,7 @@ where
|
||||||
|
|
||||||
// Do basic checks first
|
// Do basic checks first
|
||||||
check::has_inputs_and_outputs(&tx)?;
|
check::has_inputs_and_outputs(&tx)?;
|
||||||
|
check::has_enough_orchard_flags(&tx)?;
|
||||||
|
|
||||||
if req.is_mempool() && tx.has_any_coinbase_inputs() {
|
if req.is_mempool() && tx.has_any_coinbase_inputs() {
|
||||||
return Err(TransactionError::CoinbaseInMempool);
|
return Err(TransactionError::CoinbaseInMempool);
|
||||||
|
|
|
@ -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.
|
/// Check that a coinbase transaction has no PrevOut inputs, JoinSplits, or spends.
|
||||||
///
|
///
|
||||||
/// A coinbase transaction MUST NOT have any transparent inputs, JoinSplit descriptions,
|
/// A coinbase transaction MUST NOT have any transparent inputs, JoinSplit descriptions,
|
||||||
|
|
|
@ -114,6 +114,49 @@ fn fake_v5_transaction_with_orchard_actions_has_inputs_and_outputs() {
|
||||||
assert!(check::has_inputs_and_outputs(&transaction).is_ok());
|
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]
|
#[test]
|
||||||
fn v5_transaction_with_no_inputs_fails_validation() {
|
fn v5_transaction_with_no_inputs_fails_validation() {
|
||||||
let transaction = fake_v5_transactions_for_network(
|
let transaction = fake_v5_transactions_for_network(
|
||||||
|
|
Loading…
Reference in New Issue