use std::{
collections::HashMap,
future::Future,
pin::Pin,
sync::Arc,
task::{Context, Poll},
time::Duration,
};
use chrono::{DateTime, Utc};
use futures::{
stream::{FuturesUnordered, StreamExt},
FutureExt,
};
use tokio::sync::oneshot;
use tower::{
buffer::Buffer,
timeout::{error::Elapsed, Timeout},
util::BoxService,
Service, ServiceExt,
};
use tracing::Instrument;
use zcash_protocol::value::ZatBalance;
use zebra_chain::{
amount::{Amount, NonNegative},
block,
parameters::{Network, NetworkUpgrade},
primitives::Groth16Proof,
serialization::DateTime32,
transaction::{
self, HashType, SigHash, Transaction, UnminedTx, UnminedTxId, VerifiedUnminedTx,
},
transparent,
};
use zebra_node_services::mempool;
use zebra_script::{CachedFfiTransaction, Sigops};
use zebra_state as zs;
use crate::{error::TransactionError, primitives, script, BoxError};
pub mod check;
#[cfg(test)]
mod tests;
const UTXO_LOOKUP_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(6 * 60);
const MEMPOOL_OUTPUT_LOOKUP_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(60);
const POLL_MEMPOOL_DELAY: std::time::Duration = Duration::from_millis(50);
pub struct BlockTxVerifier<ZS> {
network: Network,
state: Timeout<ZS>,
script_verifier: script::Verifier,
}
impl<ZS> BlockTxVerifier<ZS>
where
ZS: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
ZS::Future: Send + 'static,
{
pub fn new(network: &Network, state: ZS) -> Self {
Self {
network: network.clone(),
state: Timeout::new(state, UTXO_LOOKUP_TIMEOUT),
script_verifier: script::Verifier,
}
}
}
pub struct MempoolTxVerifier<ZS, Mempool> {
network: Network,
state: Timeout<ZS>,
mempool: Option<Timeout<Mempool>>,
script_verifier: script::Verifier,
mempool_setup_rx: oneshot::Receiver<Mempool>,
}
impl<ZS, Mempool> MempoolTxVerifier<ZS, Mempool>
where
ZS: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
ZS::Future: Send + 'static,
Mempool: Service<mempool::Request, Response = mempool::Response, Error = BoxError>
+ Send
+ Clone
+ 'static,
Mempool::Future: Send + 'static,
{
pub fn new(network: &Network, state: ZS, mempool_setup_rx: oneshot::Receiver<Mempool>) -> Self {
Self {
network: network.clone(),
state: Timeout::new(state, UTXO_LOOKUP_TIMEOUT),
mempool: None,
script_verifier: script::Verifier,
mempool_setup_rx,
}
}
}
impl<ZS>
MempoolTxVerifier<
ZS,
Buffer<BoxService<mempool::Request, mempool::Response, BoxError>, mempool::Request>,
>
where
ZS: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
ZS::Future: Send + 'static,
{
#[cfg(test)]
pub fn new_for_tests(network: &Network, state: ZS) -> Self {
Self {
network: network.clone(),
state: Timeout::new(state, UTXO_LOOKUP_TIMEOUT),
mempool: None,
script_verifier: script::Verifier,
mempool_setup_rx: oneshot::channel().1,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BlockRequest {
pub transaction_hash: transaction::Hash,
pub transaction: Arc<Transaction>,
pub known_utxos: Arc<HashMap<transparent::OutPoint, transparent::OrderedUtxo>>,
pub height: block::Height,
pub time: DateTime<Utc>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MempoolRequest {
pub transaction: UnminedTx,
pub height: block::Height,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BlockResponse {
pub tx_id: UnminedTxId,
pub miner_fee: Option<Amount<NonNegative>>,
pub sigops: u32,
}
#[derive(Clone, Debug, PartialEq)]
pub struct MempoolResponse {
pub transaction: VerifiedUnminedTx,
pub spent_mempool_outpoints: Vec<transparent::OutPoint>,
}
#[cfg(any(test, feature = "proptest-impl"))]
impl From<VerifiedUnminedTx> for MempoolResponse {
fn from(transaction: VerifiedUnminedTx) -> Self {
MempoolResponse {
transaction,
spent_mempool_outpoints: Vec::new(),
}
}
}
impl<ZS> Service<BlockRequest> for BlockTxVerifier<ZS>
where
ZS: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
ZS::Future: Send + 'static,
{
type Response = BlockResponse;
type Error = TransactionError;
type Future =
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: BlockRequest) -> Self::Future {
let script_verifier = self.script_verifier;
let network = self.network.clone();
let state = self.state.clone();
let tx = req.transaction.clone();
let tx_id = match tx.auth_digest() {
None => UnminedTxId::Legacy(req.transaction_hash),
Some(auth_digest) => UnminedTxId::Witnessed(transaction::WtxId {
id: req.transaction_hash,
auth_digest,
}),
};
let height = req.height;
let time = req.time;
let known_utxos = req.known_utxos.clone();
let nu = NetworkUpgrade::current(&network, height);
let span = tracing::debug_span!("tx", ?tx_id);
async move {
tracing::trace!(?tx_id, ?req, "got tx verify request");
check_structure_and_network_rules(tx.as_ref(), height, &network)?;
if tx.is_coinbase() {
check::coinbase_tx_no_prevout_joinsplit_spend(&tx)?;
} else if !tx.is_valid_non_coinbase() {
return Err(TransactionError::NonCoinbaseHasCoinbaseInput);
}
if tx.is_coinbase() {
check::coinbase_expiry_height(&height, &tx, &network)?;
} else {
check::non_coinbase_expiry_height(&height, &tx)?;
}
check_transaction_invariants(tx.as_ref(), height, &network)?;
tracing::trace!(?tx_id, "passed quick checks");
check::lock_time_has_passed(&tx, height, time)?;
let (spent_utxos, spent_outputs) =
Self::block_spent_utxos(tx.clone(), known_utxos, state.clone()).await?;
let cached_ffi_transaction =
Arc::new(CachedFfiTransaction::new(tx.clone(), Arc::new(spent_outputs), nu).map_err(|_| TransactionError::UnsupportedByNetworkUpgrade(tx.version(), nu))?);
tracing::trace!(?tx_id, "got state UTXOs");
let async_checks = dispatch_version_verification(
tx.as_ref(),
nu,
script_verifier,
cached_ffi_transaction.clone()
)?;
tracing::trace!(?tx_id, "awaiting async checks...");
async_checks.check().await?;
tracing::trace!(?tx_id, "finished async checks");
let miner_fee = if tx.is_coinbase() {
None
} else {
Some(miner_fee(tx.as_ref(), &spent_utxos)?)
};
let sigops = tx.sigops().map_err(zebra_script::Error::from)?;
Ok(BlockResponse {
tx_id,
miner_fee,
sigops: sigops.saturating_add(cached_ffi_transaction.p2sh_sigops()),
})
}
.inspect(move |result| {
tracing::trace!(?tx_id, result = ?result.as_ref().map(|_tx| ()), "got tx verify result");
})
.instrument(span)
.boxed()
}
}
impl<ZS> BlockTxVerifier<ZS>
where
ZS: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
ZS::Future: Send + 'static,
{
async fn block_spent_utxos(
tx: Arc<Transaction>,
known_utxos: Arc<HashMap<transparent::OutPoint, transparent::OrderedUtxo>>,
state: Timeout<ZS>,
) -> Result<
(
HashMap<transparent::OutPoint, transparent::Utxo>,
Vec<transparent::Output>,
),
TransactionError,
> {
let inputs = tx.inputs();
let mut spent_utxos = HashMap::new();
let mut spent_outputs: Vec<Option<transparent::Output>> = vec![None; inputs.len()];
for (input_idx, input) in inputs.iter().enumerate() {
if let transparent::Input::PrevOut { outpoint, .. } = input {
tracing::trace!("awaiting outpoint lookup");
let utxo = if let Some(output) = known_utxos.get(outpoint) {
tracing::trace!("UTXO in known_utxos, discarding query");
output.utxo.clone()
} else {
let response = state
.clone()
.oneshot(zebra_state::Request::AwaitUtxo(*outpoint))
.await
.map_err(|boxed_error| match boxed_error.downcast::<Elapsed>() {
Ok(_) => TransactionError::TransparentInputNotFound,
Err(boxed_error) => TransactionError::from(boxed_error),
})?;
if let zebra_state::Response::Utxo(utxo) = response {
utxo
} else {
unreachable!("AwaitUtxo always responds with Utxo")
}
};
tracing::trace!(?utxo, "got UTXO");
spent_outputs[input_idx] = Some(utxo.output.clone());
spent_utxos.insert(*outpoint, utxo);
}
}
let spent_outputs: Vec<transparent::Output> = spent_outputs.into_iter().flatten().collect();
Ok((spent_utxos, spent_outputs))
}
}
impl<ZS, Mempool> Service<MempoolRequest> for MempoolTxVerifier<ZS, Mempool>
where
ZS: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
ZS::Future: Send + 'static,
Mempool: Service<mempool::Request, Response = mempool::Response, Error = BoxError>
+ Send
+ Clone
+ 'static,
Mempool::Future: Send + 'static,
{
type Response = MempoolResponse;
type Error = TransactionError;
type Future =
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
if self.mempool.is_none() {
if let Ok(mempool) = self.mempool_setup_rx.try_recv() {
self.mempool = Some(Timeout::new(mempool, MEMPOOL_OUTPUT_LOOKUP_TIMEOUT));
}
}
Poll::Ready(Ok(()))
}
fn call(&mut self, req: MempoolRequest) -> Self::Future {
let script_verifier = self.script_verifier;
let network = self.network.clone();
let state = self.state.clone();
let mempool = self.mempool.clone();
let tx = req.transaction.transaction.clone();
let tx_id = req.transaction.id;
let height = req.height;
let unmined_tx = req.transaction.clone();
let nu = NetworkUpgrade::current(&network, height);
let span = tracing::debug_span!("tx", ?tx_id);
async move {
tracing::trace!(?tx_id, ?req, "got tx verify request");
check_structure_and_network_rules(tx.as_ref(), height, &network)?;
if tx.is_coinbase() {
return Err(TransactionError::CoinbaseInMempool);
}
if !tx.is_valid_non_coinbase() {
return Err(TransactionError::NonCoinbaseHasCoinbaseInput);
}
check::non_coinbase_expiry_height(&height, &tx)?;
check_transaction_invariants(tx.as_ref(), height, &network)?;
tracing::trace!(?tx_id, "passed quick checks");
Self::verify_mempool_lock_time(tx.as_ref(), height, state.clone()).await?;
let (spent_utxos, spent_outputs, spent_mempool_outpoints) =
Self::mempool_spent_utxos(tx.clone(), height, state.clone(), mempool.clone()).await?;
check_maturity_height(tx.clone(), height, &network, &spent_utxos)?;
check::mempool_standard_input_scripts(tx.as_ref(), &spent_outputs)?;
let miner_fee = miner_fee(tx.as_ref(), &spent_utxos)?;
let unpaid_actions = transaction::zip317::unpaid_actions(&unmined_tx, miner_fee);
transaction::zip317::mempool_checks(unpaid_actions, miner_fee, unmined_tx.size)?;
let cached_ffi_transaction =
Arc::new(CachedFfiTransaction::new(tx.clone(), Arc::new(spent_outputs), nu).map_err(|_| TransactionError::UnsupportedByNetworkUpgrade(tx.version(), nu))?);
tracing::trace!(?tx_id, "got state UTXOs");
let mut async_checks = dispatch_version_verification(
tx.as_ref(),
nu,
script_verifier,
cached_ffi_transaction.clone()
)?;
let check_anchors_and_revealed_nullifiers_query = state
.clone()
.oneshot(zs::Request::CheckBestChainTipNullifiersAndAnchors(
unmined_tx.clone(),
))
.map(|res| {
assert!(
res? == zs::Response::ValidBestChainTipNullifiersAndAnchors,
"unexpected response to CheckBestChainTipNullifiersAndAnchors request"
);
Ok(())
});
async_checks.push(check_anchors_and_revealed_nullifiers_query);
tracing::trace!(?tx_id, "awaiting async checks...");
async_checks.check().await?;
tracing::trace!(?tx_id, "finished async checks");
let sigops = tx.sigops().map_err(zebra_script::Error::from)?;
let spent_outputs = cached_ffi_transaction.all_previous_outputs().clone();
let transaction = VerifiedUnminedTx::new(
unmined_tx,
miner_fee,
sigops,
cached_ffi_transaction.p2sh_sigops(),
spent_outputs.into(),
)?;
if let Some(mut mempool) = mempool {
tokio::spawn(async move {
tokio::time::sleep(POLL_MEMPOOL_DELAY).await;
let _ = mempool
.ready()
.await
.expect("mempool poll_ready() method should not return an error")
.call(mempool::Request::CheckForVerifiedTransactions)
.await;
});
}
Ok(MempoolResponse { transaction, spent_mempool_outpoints })
}
.inspect(move |result| {
tracing::trace!(?tx_id, result = ?result.as_ref().map(|_tx| ()), "got tx verify result");
})
.instrument(span)
.boxed()
}
}
impl<ZS, Mempool> MempoolTxVerifier<ZS, Mempool>
where
ZS: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
ZS::Future: Send + 'static,
Mempool: Service<mempool::Request, Response = mempool::Response, Error = BoxError>
+ Send
+ Clone
+ 'static,
Mempool::Future: Send + 'static,
{
async fn verify_mempool_lock_time(
tx: &Transaction,
height: block::Height,
state: Timeout<ZS>,
) -> Result<(), TransactionError> {
let next_median_time_past = if tx.lock_time_is_time() {
Some(
Self::mempool_best_chain_next_median_time_past(state)
.await?
.to_chrono(),
)
} else {
None
};
check::lock_time_has_passed(tx, height, next_median_time_past)?;
Ok(())
}
async fn mempool_best_chain_next_median_time_past(
state: Timeout<ZS>,
) -> Result<DateTime32, TransactionError> {
let query = state
.clone()
.oneshot(zs::Request::BestChainNextMedianTimePast);
if let zebra_state::Response::BestChainNextMedianTimePast(median_time_past) = query
.await
.map_err(|e| TransactionError::ValidateMempoolLockTimeError(e.to_string()))?
{
Ok(median_time_past)
} else {
unreachable!("Request::BestChainNextMedianTimePast always responds with BestChainNextMedianTimePast")
}
}
async fn mempool_spent_utxos(
tx: Arc<Transaction>,
height: block::Height,
state: Timeout<ZS>,
mempool: Option<Timeout<Mempool>>,
) -> Result<
(
HashMap<transparent::OutPoint, transparent::Utxo>,
Vec<transparent::Output>,
Vec<transparent::OutPoint>,
),
TransactionError,
> {
let inputs = tx.inputs();
let mut spent_utxos = HashMap::new();
let mut spent_outputs: Vec<Option<transparent::Output>> = vec![None; inputs.len()];
let mut spent_mempool_outpoints: Vec<(usize, transparent::OutPoint)> = Vec::new();
for (input_idx, input) in inputs.iter().enumerate() {
if let transparent::Input::PrevOut { outpoint, .. } = input {
tracing::trace!("awaiting outpoint lookup");
let query = state
.clone()
.oneshot(zs::Request::UnspentBestChainUtxo(*outpoint));
let zebra_state::Response::UnspentBestChainUtxo(utxo) = query
.await
.map_err(|_| TransactionError::TransparentInputNotFound)?
else {
unreachable!("UnspentBestChainUtxo always responds with Option<Utxo>")
};
let Some(utxo) = utxo else {
spent_mempool_outpoints.push((input_idx, *outpoint));
continue;
};
tracing::trace!(?utxo, "got UTXO");
spent_outputs[input_idx] = Some(utxo.output.clone());
spent_utxos.insert(*outpoint, utxo);
}
}
if let Some(mempool) = mempool {
for &(input_idx, spent_mempool_outpoint) in &spent_mempool_outpoints {
let query = mempool
.clone()
.oneshot(mempool::Request::AwaitOutput(spent_mempool_outpoint));
let output = match query.await {
Ok(mempool::Response::UnspentOutput(output)) => output,
Ok(_) => unreachable!("UnspentOutput always responds with UnspentOutput"),
Err(err) => {
return match err.downcast::<Elapsed>() {
Ok(_) => Err(TransactionError::TransparentInputNotFound),
Err(err) => Err(err.into()),
};
}
};
spent_outputs[input_idx] = Some(output.clone());
spent_utxos.insert(
spent_mempool_outpoint,
transparent::Utxo::new(output, height, false),
);
}
} else if !spent_mempool_outpoints.is_empty() {
return Err(TransactionError::TransparentInputNotFound);
}
let spent_outputs: Vec<transparent::Output> = spent_outputs.into_iter().flatten().collect();
let spent_mempool_outpoints: Vec<transparent::OutPoint> = spent_mempool_outpoints
.into_iter()
.map(|(_, op)| op)
.collect();
Ok((spent_utxos, spent_outputs, spent_mempool_outpoints))
}
}
fn check_structure_and_network_rules(
tx: &Transaction,
height: block::Height,
network: &Network,
) -> Result<(), TransactionError> {
let network_upgrade = NetworkUpgrade::current(network, height);
check::has_inputs_and_outputs(tx)?;
check::has_enough_orchard_flags(tx)?;
check::has_enough_ironwood_flags(tx)?;
check::orchard_cross_address_disabled(tx)?;
check::orchard_value_balance_non_negative(tx, network_upgrade)?;
check::coinbase_orchard_component_empty(tx, network_upgrade)?;
check::consensus_branch_id(tx, height, network)?;
if network.is_orchard_temporarily_disabled(height) && tx.orchard_shielded_data().is_some() {
return Err(TransactionError::Other(
"transaction has Orchard actions (temporarily disabled)".into(),
));
}
if network.orchard_canonical_proof_size_rule_active(height) {
if let Some(orchard_shielded_data) = tx.orchard_shielded_data() {
if !orchard_shielded_data.proof_size_is_canonical() {
return Err(TransactionError::OrchardProofSize);
}
}
}
if let Some(ironwood_shielded_data) = tx.ironwood_shielded_data() {
if !ironwood_shielded_data.proof_size_is_canonical() {
return Err(TransactionError::IronwoodProofSize);
}
}
Ok(())
}
fn check_transaction_invariants(
tx: &Transaction,
height: block::Height,
network: &Network,
) -> Result<(), TransactionError> {
check::joinsplit_has_vpub_zero(tx)?;
check::disabled_add_to_sprout_pool(tx, height, network)?;
check::spend_conflicts(tx)?;
Ok(())
}
fn check_maturity_height(
tx: Arc<Transaction>,
height: block::Height,
network: &Network,
spent_utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
) -> Result<(), TransactionError> {
check::tx_transparent_coinbase_spends_maturity(
network,
tx,
height,
Arc::new(HashMap::new()),
spent_utxos,
)
}
fn dispatch_version_verification(
tx: &Transaction,
nu: NetworkUpgrade,
script_verifier: script::Verifier,
cached_ffi_transaction: Arc<CachedFfiTransaction>,
) -> Result<AsyncChecks, TransactionError> {
match tx {
Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => {
tracing::debug!(?tx, "got transaction with wrong version");
Err(TransactionError::WrongVersion)
}
Transaction::V4 { joinsplit_data, .. } => verify_v4_transaction(
tx,
nu,
script_verifier,
cached_ffi_transaction,
joinsplit_data,
),
Transaction::V5 { .. } => {
verify_v5_transaction(tx, nu, script_verifier, cached_ffi_transaction)
}
Transaction::V6 { .. } => {
verify_v6_transaction(tx, nu, script_verifier, cached_ffi_transaction)
}
}
}
#[allow(clippy::unwrap_in_result)]
fn verify_v4_transaction(
tx: &Transaction,
nu: NetworkUpgrade,
script_verifier: script::Verifier,
cached_ffi_transaction: Arc<CachedFfiTransaction>,
joinsplit_data: &Option<transaction::JoinSplitData<Groth16Proof>>,
) -> Result<AsyncChecks, TransactionError> {
verify_v4_transaction_network_upgrade(tx, nu)?;
let sapling_bundle = cached_ffi_transaction.sighasher().sapling_bundle();
let sighash = cached_ffi_transaction
.sighasher()
.sighash(HashType::ALL, None);
Ok(
verify_transparent_inputs_and_outputs(tx, script_verifier, cached_ffi_transaction)?
.and(verify_sprout_shielded_data(joinsplit_data, &sighash)?)
.and(verify_sapling_bundle(sapling_bundle, &sighash)),
)
}
fn verify_v4_transaction_network_upgrade(
transaction: &Transaction,
network_upgrade: NetworkUpgrade,
) -> Result<(), TransactionError> {
match network_upgrade {
NetworkUpgrade::Sapling
| NetworkUpgrade::Blossom
| NetworkUpgrade::Heartwood
| NetworkUpgrade::Canopy
| NetworkUpgrade::Nu5
| NetworkUpgrade::Nu6
| NetworkUpgrade::Nu6_1
| NetworkUpgrade::Nu6_2
| NetworkUpgrade::Nu6_3 => Ok(()),
#[cfg(zcash_unstable = "zfuture")]
NetworkUpgrade::ZFuture => Ok(()),
NetworkUpgrade::Genesis
| NetworkUpgrade::BeforeOverwinter
| NetworkUpgrade::Overwinter
| NetworkUpgrade::Nu7 => Err(TransactionError::UnsupportedByNetworkUpgrade(
transaction.version(),
network_upgrade,
)),
}
}
#[allow(clippy::unwrap_in_result)]
fn verify_v5_transaction(
tx: &Transaction,
nu: NetworkUpgrade,
script_verifier: script::Verifier,
cached_ffi_transaction: Arc<CachedFfiTransaction>,
) -> Result<AsyncChecks, TransactionError> {
verify_v5_transaction_network_upgrade(tx, nu)?;
let sapling_bundle = cached_ffi_transaction.sighasher().sapling_bundle();
let orchard_bundle = cached_ffi_transaction.sighasher().orchard_bundle();
let sighash = cached_ffi_transaction
.sighasher()
.sighash(HashType::ALL, None);
Ok(
verify_transparent_inputs_and_outputs(tx, script_verifier, cached_ffi_transaction)?
.and(verify_sapling_bundle(sapling_bundle, &sighash))
.and(verify_orchard_bundle(orchard_bundle, &sighash, nu)),
)
}
fn verify_v5_transaction_network_upgrade(
transaction: &Transaction,
network_upgrade: NetworkUpgrade,
) -> Result<(), TransactionError> {
match network_upgrade {
NetworkUpgrade::Nu5
| NetworkUpgrade::Nu6
| NetworkUpgrade::Nu6_1
| NetworkUpgrade::Nu6_2
| NetworkUpgrade::Nu6_3
| NetworkUpgrade::Nu7 => Ok(()),
#[cfg(zcash_unstable = "zfuture")]
NetworkUpgrade::ZFuture => Ok(()),
NetworkUpgrade::Genesis
| NetworkUpgrade::BeforeOverwinter
| NetworkUpgrade::Overwinter
| NetworkUpgrade::Sapling
| NetworkUpgrade::Blossom
| NetworkUpgrade::Heartwood
| NetworkUpgrade::Canopy => Err(TransactionError::UnsupportedByNetworkUpgrade(
transaction.version(),
network_upgrade,
)),
}
}
fn verify_v6_transaction(
tx: &Transaction,
nu: NetworkUpgrade,
script_verifier: script::Verifier,
cached_ffi_transaction: Arc<CachedFfiTransaction>,
) -> Result<AsyncChecks, TransactionError> {
verify_v6_transaction_network_upgrade(tx, nu)?;
let sapling_bundle = cached_ffi_transaction.sighasher().sapling_bundle();
let orchard_bundle = cached_ffi_transaction.sighasher().orchard_bundle();
let ironwood_bundle = cached_ffi_transaction.sighasher().ironwood_bundle();
let sighash = cached_ffi_transaction
.sighasher()
.sighash(HashType::ALL, None);
Ok(
verify_transparent_inputs_and_outputs(tx, script_verifier, cached_ffi_transaction)?
.and(verify_sapling_bundle(sapling_bundle, &sighash))
.and(verify_orchard_v6_bundle(orchard_bundle, &sighash))
.and(verify_orchard_v6_bundle(ironwood_bundle, &sighash)),
)
}
fn verify_v6_transaction_network_upgrade(
transaction: &Transaction,
network_upgrade: NetworkUpgrade,
) -> Result<(), TransactionError> {
match network_upgrade {
NetworkUpgrade::Nu6_3 | NetworkUpgrade::Nu7 => Ok(()),
#[cfg(zcash_unstable = "zfuture")]
NetworkUpgrade::ZFuture => Ok(()),
NetworkUpgrade::Genesis
| NetworkUpgrade::BeforeOverwinter
| NetworkUpgrade::Overwinter
| NetworkUpgrade::Sapling
| NetworkUpgrade::Blossom
| NetworkUpgrade::Heartwood
| NetworkUpgrade::Canopy
| NetworkUpgrade::Nu5
| NetworkUpgrade::Nu6
| NetworkUpgrade::Nu6_1
| NetworkUpgrade::Nu6_2 => Err(TransactionError::UnsupportedByNetworkUpgrade(
transaction.version(),
network_upgrade,
)),
}
}
fn verify_transparent_inputs_and_outputs(
tx: &Transaction,
script_verifier: script::Verifier,
cached_ffi_transaction: Arc<CachedFfiTransaction>,
) -> Result<AsyncChecks, TransactionError> {
if tx.is_coinbase() {
Ok(AsyncChecks::new())
} else {
let inputs = tx.inputs();
let script_checks = (0..inputs.len())
.map(move |input_index| {
let request = script::Request {
cached_ffi_transaction: cached_ffi_transaction.clone(),
input_index,
};
script_verifier.oneshot(request)
})
.collect();
Ok(script_checks)
}
}
fn verify_sprout_shielded_data(
joinsplit_data: &Option<transaction::JoinSplitData<Groth16Proof>>,
shielded_sighash: &SigHash,
) -> Result<AsyncChecks, TransactionError> {
let mut checks = AsyncChecks::new();
if let Some(joinsplit_data) = joinsplit_data {
for joinsplit in joinsplit_data.joinsplits() {
checks.push(primitives::groth16::JOINSPLIT_VERIFIER.oneshot(
primitives::groth16::Item::from_joinsplit(joinsplit, &joinsplit_data.pub_key)?,
));
}
let ed25519_verifier = primitives::ed25519::VERIFIER.clone();
let ed25519_item = (joinsplit_data.pub_key, joinsplit_data.sig, shielded_sighash).into();
checks.push(ed25519_verifier.oneshot(ed25519_item));
}
Ok(checks)
}
fn verify_sapling_bundle(
bundle: Option<sapling_crypto::Bundle<sapling_crypto::bundle::Authorized, ZatBalance>>,
sighash: &SigHash,
) -> AsyncChecks {
let mut async_checks = AsyncChecks::new();
if let Some(bundle) = bundle {
async_checks.push(
primitives::sapling::VERIFIER
.clone()
.oneshot(primitives::sapling::Item::new(bundle, *sighash)),
);
}
async_checks
}
fn verify_orchard_bundle(
bundle: Option<::orchard::bundle::Bundle<::orchard::bundle::Authorized, ZatBalance>>,
sighash: &SigHash,
network_upgrade: NetworkUpgrade,
) -> AsyncChecks {
queue_orchard_bundle(
|| primitives::halo2::orchard_v5_verifier_for(network_upgrade),
bundle,
sighash,
)
}
fn verify_orchard_v6_bundle(
bundle: Option<::orchard::bundle::Bundle<::orchard::bundle::Authorized, ZatBalance>>,
sighash: &SigHash,
) -> AsyncChecks {
queue_orchard_bundle(primitives::halo2::orchard_v6_verifier, bundle, sighash)
}
fn queue_orchard_bundle(
select_verifier: impl FnOnce() -> &'static primitives::halo2::VerifierService,
bundle: Option<::orchard::bundle::Bundle<::orchard::bundle::Authorized, ZatBalance>>,
sighash: &SigHash,
) -> AsyncChecks {
let mut async_checks = AsyncChecks::new();
if let Some(bundle) = bundle {
async_checks.push(
select_verifier()
.clone()
.oneshot(primitives::halo2::Item::new(bundle, *sighash)),
);
}
async_checks
}
fn miner_fee(
tx: &Transaction,
spent_utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
) -> Result<Amount<NonNegative>, TransactionError> {
match tx.value_balance(spent_utxos) {
Ok(value_balance) => value_balance
.remaining_transaction_value()
.map_err(|_| TransactionError::IncorrectFee),
Err(_) => Err(TransactionError::IncorrectFee),
}
}
struct AsyncChecks(FuturesUnordered<Pin<Box<dyn Future<Output = Result<(), BoxError>> + Send>>>);
impl AsyncChecks {
pub fn new() -> Self {
AsyncChecks(FuturesUnordered::new())
}
pub fn push(&mut self, check: impl Future<Output = Result<(), BoxError>> + Send + 'static) {
self.0.push(check.boxed());
}
pub fn and(mut self, checks: AsyncChecks) -> Self {
self.0.extend(checks.0);
self
}
async fn check(mut self) -> Result<(), BoxError> {
while let Some(check) = self.0.next().await {
tracing::trace!(?check, remaining = self.0.len());
check?;
}
Ok(())
}
}
impl<F> FromIterator<F> for AsyncChecks
where
F: Future<Output = Result<(), BoxError>> + Send + 'static,
{
fn from_iter<I>(iterator: I) -> Self
where
I: IntoIterator<Item = F>,
{
AsyncChecks(iterator.into_iter().map(FutureExt::boxed).collect())
}
}