Update `has_inputs_and_outputs()` for new consensus rules (#2398)

* update the has_inputs_and_outputs() to new rules

* apply clippy suggestions

* add some TODOs
This commit is contained in:
Alfredo Garcia 2021-06-28 19:28:49 -03:00 committed by GitHub
parent 19fa36049f
commit c06cd19239
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 56 additions and 8 deletions

View File

@ -483,4 +483,11 @@ impl Transaction {
.map(orchard::ShieldedData::nullifiers)
.flatten()
}
/// Access the [`orchard::Flags`] in this transaction, if there is any,
/// regardless of version.
pub fn orchard_flags(&self) -> Option<orchard::shielded_data::Flags> {
self.orchard_shielded_data()
.map(|orchard_shielded_data| orchard_shielded_data.flags)
}
}

View File

@ -13,12 +13,14 @@ use crate::error::TransactionError;
/// Checks that the transaction has inputs and outputs.
///
/// For `Transaction::V4`:
/// * at least one of `tx_in_count`, `nSpendsSapling`, and `nJoinSplit` MUST be non-zero.
/// * at least one of `tx_out_count`, `nOutputsSapling`, and `nJoinSplit` MUST be non-zero.
/// * At least one of `tx_in_count`, `nSpendsSapling`, and `nJoinSplit` MUST be non-zero.
/// * At least one of `tx_out_count`, `nOutputsSapling`, and `nJoinSplit` MUST be non-zero.
///
/// For `Transaction::V5`:
/// * at least one of `tx_in_count`, `nSpendsSapling`, and `nActionsOrchard` MUST be non-zero.
/// * at least one of `tx_out_count`, `nOutputsSapling`, and `nActionsOrchard` MUST be non-zero.
/// * This condition must hold: `tx_in_count` > 0 or `nSpendsSapling` > 0 or
/// (`nActionsOrchard` > 0 and `enableSpendsOrchard` = 1)
/// * This condition must hold: `tx_out_count` > 0 or `nOutputsSapling` > 0 or
/// (`nActionsOrchard` > 0 and `enableOutputsOrchard` = 1)
///
/// This check counts both `Coinbase` and `PrevOut` transparent inputs.
///
@ -30,10 +32,22 @@ pub fn has_inputs_and_outputs(tx: &Transaction) -> Result<(), TransactionError>
let n_spends_sapling = tx.sapling_spends_per_anchor().count();
let n_outputs_sapling = tx.sapling_outputs().count();
let n_actions_orchard = tx.orchard_actions().count();
let flags_orchard = tx.orchard_flags().unwrap_or_else(Flags::empty);
if tx_in_count + n_spends_sapling + n_joinsplit + n_actions_orchard == 0 {
// TODO: Improve the code to express the spec rules better #2410.
if tx_in_count
+ n_spends_sapling
+ n_joinsplit
+ (n_actions_orchard > 0 && flags_orchard.contains(Flags::ENABLE_SPENDS)) as usize
== 0
{
Err(TransactionError::NoInputs)
} else if tx_out_count + n_outputs_sapling + n_joinsplit + n_actions_orchard == 0 {
} else if tx_out_count
+ n_outputs_sapling
+ n_joinsplit
+ (n_actions_orchard > 0 && flags_orchard.contains(Flags::ENABLE_OUTPUTS)) as usize
== 0
{
Err(TransactionError::NoOutputs)
} else {
Ok(())

View File

@ -86,8 +86,35 @@ fn fake_v5_transaction_with_orchard_actions_has_inputs_and_outputs() {
// guaranteed structurally by `orchard::ShieldedData`)
insert_fake_orchard_shielded_data(&mut transaction);
// If a transaction has at least one Orchard shielded action, it should be considered to have
// inputs and/or outputs
// The check will fail if the transaction has no flags
assert_eq!(
check::has_inputs_and_outputs(&transaction),
Err(TransactionError::NoInputs)
);
// If we add ENABLE_SPENDS flag it will pass the inputs check but fails with the outputs
// TODO: Avoid new calls to `insert_fake_orchard_shielded_data` for each check #2409.
let shielded_data = insert_fake_orchard_shielded_data(&mut transaction);
shielded_data.flags = orchard::Flags::ENABLE_SPENDS;
assert_eq!(
check::has_inputs_and_outputs(&transaction),
Err(TransactionError::NoOutputs)
);
// If we add ENABLE_OUTPUTS flag it will pass the outputs check but fails with the inputs
let shielded_data = insert_fake_orchard_shielded_data(&mut transaction);
shielded_data.flags = orchard::Flags::ENABLE_OUTPUTS;
assert_eq!(
check::has_inputs_and_outputs(&transaction),
Err(TransactionError::NoInputs)
);
// Finally make it valid by adding both required flags
let shielded_data = insert_fake_orchard_shielded_data(&mut transaction);
shielded_data.flags = orchard::Flags::ENABLE_SPENDS | orchard::Flags::ENABLE_OUTPUTS;
assert!(check::has_inputs_and_outputs(&transaction).is_ok());
}