use zcash_primitives::transaction::Transaction;
use zcash_protocol::value::Zatoshis;
use zcash_protocol::zip318::{PoolMigrationConstants, Zip318Classification, Zip318Evidence};
use crate::decrypt::{DecryptedOutput, TransferType};
#[cfg(feature = "orchard")]
pub fn classify_decrypted_tx<AccountId, C>(
tx: &Transaction,
orchard_outputs: &[DecryptedOutput<(orchard::Note, orchard::ValuePool), AccountId>],
ironwood_outputs: &[DecryptedOutput<(orchard::Note, orchard::ValuePool), AccountId>],
constants: &C,
) -> Zip318Classification
where
C: PoolMigrationConstants + ?Sized,
{
zcash_protocol::zip318::classify(
&evidence_from_decrypted_tx(tx, orchard_outputs, ironwood_outputs, constants),
constants,
)
}
#[cfg(feature = "orchard")]
fn evidence_from_decrypted_tx<AccountId, C>(
tx: &Transaction,
orchard_outputs: &[DecryptedOutput<(orchard::Note, orchard::ValuePool), AccountId>],
ironwood_outputs: &[DecryptedOutput<(orchard::Note, orchard::ValuePool), AccountId>],
constants: &C,
) -> Zip318Evidence
where
C: PoolMigrationConstants + ?Sized,
{
Zip318Evidence::default()
.with_source_actions(Some(action_count(
tx.orchard_bundle().map(|b| b.actions().len()),
)))
.with_destination_actions(Some(action_count(
tx.ironwood_bundle().map(|b| b.actions().len()),
)))
.with_other_bundles_present(Some(other_bundles_present(tx)))
.with_source_is_send_to_self(Some(is_send_to_self(orchard_outputs)))
.with_sole_destination_value(sole_destination_value(ironwood_outputs))
.with_expiry_is_canonical(Some(
constants.is_canonical_expiry_value(tx.expiry_height()),
))
}
#[cfg(feature = "orchard")]
fn action_count(bundle_actions: Option<usize>) -> usize {
bundle_actions.unwrap_or(0)
}
#[cfg(feature = "orchard")]
fn other_bundles_present(tx: &Transaction) -> bool {
tx.transparent_bundle()
.is_some_and(|b| !b.vin.is_empty() || !b.vout.is_empty())
|| tx
.sapling_bundle()
.is_some_and(|b| !b.shielded_spends().is_empty() || !b.shielded_outputs().is_empty())
}
#[cfg(feature = "orchard")]
fn is_send_to_self<AccountId>(
orchard_outputs: &[DecryptedOutput<(orchard::Note, orchard::ValuePool), AccountId>],
) -> bool {
let mut any_internal = false;
for output in orchard_outputs {
match output.transfer_type() {
TransferType::AccountInternal => any_internal = true,
TransferType::Incoming | TransferType::Outgoing | TransferType::WalletInternal => {
return false;
}
}
}
any_internal
}
#[cfg(feature = "orchard")]
fn sole_destination_value<AccountId>(
ironwood_outputs: &[DecryptedOutput<(orchard::Note, orchard::ValuePool), AccountId>],
) -> Option<Zatoshis> {
let [output] = ironwood_outputs else {
return None;
};
let (note, _pool) = output.note();
Zatoshis::from_u64(note.value().inner()).ok()
}