From f270fd2de69bc94fbf388d95038c886f3e674343 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Mon, 31 Jan 2022 12:28:42 -0300 Subject: [PATCH] Prepare for changes in ZIP-244 (#3415) * Add all_previous_outputs; load UTXOs in transaction verifier * Remove UTXO loading and returning from script.rs * Don't pass state service to script verifier * Remove output from is_valid() * Refactor loading UTXOs to separate function * Pass all_previous_output to sighash * Apply suggestions from code review Co-authored-by: teor * Create AwaitUtxo only when needed; formatting * Add comments about output vectors in tests * Change sighash() to receive reference and avoid cloning * Expand comments Co-authored-by: teor Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .../src/primitives/zcash_primitives.rs | 10 +- zebra-chain/src/transaction.rs | 12 +- zebra-chain/src/transaction/sighash.rs | 30 ++- zebra-chain/src/transaction/tests/vectors.rs | 118 ++++++++---- zebra-consensus/src/block/tests.rs | 13 +- zebra-consensus/src/chain.rs | 4 +- zebra-consensus/src/script.rs | 84 ++------ zebra-consensus/src/transaction.rs | 129 +++++++++---- zebra-consensus/src/transaction/tests.rs | 84 ++++---- zebra-consensus/src/transaction/tests/prop.rs | 4 +- zebra-script/src/lib.rs | 182 +++++++++--------- 11 files changed, 367 insertions(+), 303 deletions(-) diff --git a/zebra-chain/src/primitives/zcash_primitives.rs b/zebra-chain/src/primitives/zcash_primitives.rs index 407066f06..4b2da31ff 100644 --- a/zebra-chain/src/primitives/zcash_primitives.rs +++ b/zebra-chain/src/primitives/zcash_primitives.rs @@ -87,18 +87,20 @@ pub(crate) fn sighash( trans: &Transaction, hash_type: HashType, network_upgrade: NetworkUpgrade, - input: Option<(&transparent::Output, &transparent::Input, usize)>, + all_previous_outputs: &[transparent::Output], + input_index: Option, ) -> SigHash { let alt_tx = convert_tx_to_librustzcash(trans, network_upgrade) .expect("zcash_primitives and Zebra transaction formats must be compatible"); let script: zcash_primitives::legacy::Script; - let signable_input = match input { - Some((output, _, idx)) => { + let signable_input = match input_index { + Some(input_index) => { + let output = all_previous_outputs[input_index].clone(); script = (&output.lock_script).into(); zcash_primitives::transaction::sighash::SignableInput::Transparent( zcash_primitives::transaction::sighash::TransparentInput::new( - idx, + input_index, &script, output .value diff --git a/zebra-chain/src/transaction.rs b/zebra-chain/src/transaction.rs index 0234c8971..49457d03e 100644 --- a/zebra-chain/src/transaction.rs +++ b/zebra-chain/src/transaction.rs @@ -191,9 +191,17 @@ impl Transaction { &self, network_upgrade: NetworkUpgrade, hash_type: sighash::HashType, - input: Option<(u32, transparent::Output)>, + all_previous_outputs: &[transparent::Output], + input: Option, ) -> SigHash { - sighash::SigHasher::new(self, hash_type, network_upgrade, input).sighash() + sighash::SigHasher::new( + self, + hash_type, + network_upgrade, + all_previous_outputs, + input, + ) + .sighash() } /// Compute the authorizing data commitment of this transaction as specified diff --git a/zebra-chain/src/transaction/sighash.rs b/zebra-chain/src/transaction/sighash.rs index fce33f049..5395c284f 100644 --- a/zebra-chain/src/transaction/sighash.rs +++ b/zebra-chain/src/transaction/sighash.rs @@ -43,7 +43,8 @@ pub(super) struct SigHasher<'a> { trans: &'a Transaction, hash_type: HashType, network_upgrade: NetworkUpgrade, - input: Option<(transparent::Output, &'a transparent::Input, usize)>, + all_previous_outputs: &'a [transparent::Output], + input_index: Option, } impl<'a> SigHasher<'a> { @@ -51,22 +52,15 @@ impl<'a> SigHasher<'a> { trans: &'a Transaction, hash_type: HashType, network_upgrade: NetworkUpgrade, - input: Option<(u32, transparent::Output)>, + all_previous_outputs: &'a [transparent::Output], + input_index: Option, ) -> Self { - let input = if let Some((index, prevout)) = input { - let index = index as usize; - let inputs = trans.inputs(); - - Some((prevout, &inputs[index], index)) - } else { - None - }; - SigHasher { trans, hash_type, network_upgrade, - input, + all_previous_outputs, + input_index, } } @@ -83,10 +77,12 @@ impl<'a> SigHasher<'a> { /// Compute a signature hash using librustzcash. fn hash_sighash_librustzcash(&self) -> SigHash { - let input = self - .input - .as_ref() - .map(|(output, input, idx)| (output, *input, *idx)); - sighash(self.trans, self.hash_type, self.network_upgrade, input) + sighash( + self.trans, + self.hash_type, + self.network_upgrade, + self.all_previous_outputs, + self.input_index, + ) } } diff --git a/zebra-chain/src/transaction/tests/vectors.rs b/zebra-chain/src/transaction/tests/vectors.rs index 843b255c2..bef3cdf28 100644 --- a/zebra-chain/src/transaction/tests/vectors.rs +++ b/zebra-chain/src/transaction/tests/vectors.rs @@ -612,6 +612,7 @@ fn test_vec143_1() -> Result<()> { &transaction, HashType::ALL, NetworkUpgrade::Overwinter, + Default::default(), None, ); @@ -639,12 +640,16 @@ fn test_vec143_2() -> Result<()> { let value = hex::decode("2f6e04963b4c0100")?.zcash_deserialize_into::>()?; let lock_script = Script::new(&hex::decode("53")?); let input_ind = 1; + let output = transparent::Output { value, lock_script }; + let all_previous_outputs = vec![output.clone(), output]; let hasher = SigHasher::new( &transaction, HashType::SINGLE, NetworkUpgrade::Overwinter, - Some((input_ind, transparent::Output { value, lock_script })), + // Pre-V5, only the matching output matters, so just use clones for the rest + &all_previous_outputs, + Some(input_ind), ); let hash = hasher.sighash(); @@ -669,7 +674,13 @@ fn test_vec243_1() -> Result<()> { let transaction = ZIP243_1.zcash_deserialize_into::()?; - let hasher = SigHasher::new(&transaction, HashType::ALL, NetworkUpgrade::Sapling, None); + let hasher = SigHasher::new( + &transaction, + HashType::ALL, + NetworkUpgrade::Sapling, + Default::default(), + None, + ); let hash = hasher.sighash(); let expected = "63d18534de5f2d1c9e169b73f9c783718adbef5c8a7d55b5e7a37affa1dd3ff3"; @@ -687,6 +698,7 @@ fn test_vec243_1() -> Result<()> { &transaction, HashType::ALL, NetworkUpgrade::Sapling, + &[], None, ); let result = hex::encode(alt_sighash); @@ -704,12 +716,16 @@ fn test_vec243_2() -> Result<()> { let value = hex::decode("adedf02996510200")?.zcash_deserialize_into::>()?; let lock_script = Script::new(&[]); let input_ind = 1; + let output = transparent::Output { value, lock_script }; + let all_previous_outputs = vec![output.clone(), output]; let hasher = SigHasher::new( &transaction, HashType::NONE, NetworkUpgrade::Sapling, - Some((input_ind, transparent::Output { value, lock_script })), + // Pre-V5, only the matching output matters, so just use clones for the rest + &all_previous_outputs, + Some(input_ind), ); let hash = hasher.sighash(); @@ -727,14 +743,14 @@ fn test_vec243_2() -> Result<()> { let lock_script = Script::new(&[]); let prevout = transparent::Output { value, lock_script }; let index = input_ind as usize; - let inputs = transaction.inputs(); - let input = Some((&prevout, &inputs[index], index)); let alt_sighash = crate::primitives::zcash_primitives::sighash( &transaction, HashType::NONE, NetworkUpgrade::Sapling, - input, + // Pre-V5, only the matching output matters, so just use clones for the rest + &[prevout.clone(), prevout], + Some(index), ); let result = hex::encode(alt_sighash); assert_eq!(expected, result); @@ -753,12 +769,14 @@ fn test_vec243_3() -> Result<()> { "76a914507173527b4c3318a2aecd793bf1cfed705950cf88ac", )?); let input_ind = 0; + let all_previous_outputs = vec![transparent::Output { value, lock_script }]; let hasher = SigHasher::new( &transaction, HashType::ALL, NetworkUpgrade::Sapling, - Some((input_ind, transparent::Output { value, lock_script })), + &all_previous_outputs, + Some(input_ind), ); let hash = hasher.sighash(); @@ -778,14 +796,13 @@ fn test_vec243_3() -> Result<()> { )?); let prevout = transparent::Output { value, lock_script }; let index = input_ind as usize; - let inputs = transaction.inputs(); - let input = Some((&prevout, &inputs[index], index)); let alt_sighash = crate::primitives::zcash_primitives::sighash( &transaction, HashType::ALL, NetworkUpgrade::Sapling, - input, + &[prevout], + Some(index), ); let result = hex::encode(alt_sighash); assert_eq!(expected, result); @@ -799,22 +816,28 @@ fn zip143_sighash() -> Result<()> { for (i, test) in zip0143::TEST_VECTORS.iter().enumerate() { let transaction = test.tx.zcash_deserialize_into::()?; - let input = match test.transparent_input { - Some(transparent_input) => Some(( - transparent_input, - transparent::Output { + let (input_index, output) = match test.transparent_input { + Some(transparent_input) => ( + Some(transparent_input as usize), + Some(transparent::Output { value: test.amount.try_into()?, lock_script: transparent::Script::new(test.script_code.as_ref()), - }, - )), - None => None, + }), + ), + None => (None, None), + }; + // Pre-V5, only the matching output matters, so just use clones for the rest + let all_previous_outputs: Vec<_> = match output { + Some(output) => (0..=input_index.unwrap()).map(|_| output.clone()).collect(), + None => vec![], }; let result = hex::encode( transaction.sighash( NetworkUpgrade::from_branch_id(test.consensus_branch_id) .expect("must be a valid branch ID"), HashType::from_bits(test.hash_type).expect("must be a valid HashType"), - input, + &all_previous_outputs, + input_index, ), ); let expected = hex::encode(test.sighash); @@ -830,22 +853,28 @@ fn zip243_sighash() -> Result<()> { for (i, test) in zip0243::TEST_VECTORS.iter().enumerate() { let transaction = test.tx.zcash_deserialize_into::()?; - let input = match test.transparent_input { - Some(transparent_input) => Some(( - transparent_input, - transparent::Output { + let (input_index, output) = match test.transparent_input { + Some(transparent_input) => ( + Some(transparent_input as usize), + Some(transparent::Output { value: test.amount.try_into()?, lock_script: transparent::Script::new(test.script_code.as_ref()), - }, - )), - None => None, + }), + ), + None => (None, None), + }; + // Pre-V5, only the matching output matters, so just use clones for the rest + let all_previous_outputs: Vec<_> = match output { + Some(output) => (0..=input_index.unwrap()).map(|_| output.clone()).collect(), + None => vec![], }; let result = hex::encode( transaction.sighash( NetworkUpgrade::from_branch_id(test.consensus_branch_id) .expect("must be a valid branch ID"), HashType::from_bits(test.hash_type).expect("must be a valid HashType"), - input, + &all_previous_outputs, + input_index, ), ); let expected = hex::encode(test.sighash); @@ -861,22 +890,35 @@ fn zip244_sighash() -> Result<()> { for (i, test) in zip0244::TEST_VECTORS.iter().enumerate() { let transaction = test.tx.zcash_deserialize_into::()?; - let input = match test.amount { - Some(amount) => Some(( - test.transparent_input - .expect("test vector must have transparent_input when it has amount"), - transparent::Output { + let (input_index, output) = match test.amount { + Some(amount) => ( + Some( + test.transparent_input + .expect("test vector must have transparent_input when it has amount") + as usize, + ), + Some(transparent::Output { value: amount.try_into()?, lock_script: transparent::Script::new( test.script_code .as_ref() .expect("test vector must have script_code when it has amount"), ), - }, - )), - None => None, + }), + ), + None => (None, None), }; - let result = hex::encode(transaction.sighash(NetworkUpgrade::Nu5, HashType::ALL, input)); + // Pre-V5, only the matching output matters, so just use clones for the rest + let all_previous_outputs: Vec<_> = match output { + Some(output) => (0..=input_index.unwrap()).map(|_| output.clone()).collect(), + None => vec![], + }; + let result = hex::encode(transaction.sighash( + NetworkUpgrade::Nu5, + HashType::ALL, + &all_previous_outputs, + input_index, + )); let expected = hex::encode(test.sighash_all); assert_eq!(expected, result, "test #{}: sighash does not match", i); } @@ -913,7 +955,8 @@ fn binding_signatures_for_network(network: Network) { .. } => { if let Some(sapling_shielded_data) = sapling_shielded_data { - let shielded_sighash = tx.sighash(upgrade, HashType::ALL, None); + let shielded_sighash = + tx.sighash(upgrade, HashType::ALL, Default::default(), None); let bvk = redjubjub::VerificationKey::try_from( sapling_shielded_data.binding_verification_key(), @@ -932,7 +975,8 @@ fn binding_signatures_for_network(network: Network) { .. } => { if let Some(sapling_shielded_data) = sapling_shielded_data { - let shielded_sighash = tx.sighash(upgrade, HashType::ALL, None); + let shielded_sighash = + tx.sighash(upgrade, HashType::ALL, Default::default(), None); let bvk = redjubjub::VerificationKey::try_from( sapling_shielded_data.binding_verification_key(), diff --git a/zebra-consensus/src/block/tests.rs b/zebra-consensus/src/block/tests.rs index bb246bbcb..c75c3cd7c 100644 --- a/zebra-consensus/src/block/tests.rs +++ b/zebra-consensus/src/block/tests.rs @@ -125,8 +125,8 @@ async fn check_transcripts() -> Result<(), Report> { let network = Network::Mainnet; let state_service = zebra_state::init_test(network); - let script = script::Verifier::new(state_service.clone()); - let transaction = transaction::Verifier::new(network, script); + let script = script::Verifier::new(); + let transaction = transaction::Verifier::new(network, state_service.clone(), script); let transaction = Buffer::new(BoxService::new(transaction), 1); let block_verifier = Buffer::new( BlockVerifier::new(network, state_service.clone(), transaction), @@ -635,7 +635,8 @@ fn legacy_sigops_count_for_large_generated_blocks() { let block = large_single_transaction_block(); let mut legacy_sigop_count = 0; for transaction in block.transactions { - let cached_ffi_transaction = Arc::new(CachedFfiTransaction::new(transaction.clone())); + let cached_ffi_transaction = + Arc::new(CachedFfiTransaction::new(transaction.clone(), Vec::new())); let tx_sigop_count = cached_ffi_transaction.legacy_sigop_count(); assert_eq!(tx_sigop_count, Ok(0)); legacy_sigop_count += tx_sigop_count.expect("unexpected invalid sigop count"); @@ -646,7 +647,8 @@ fn legacy_sigops_count_for_large_generated_blocks() { let block = large_multi_transaction_block(); let mut legacy_sigop_count = 0; for transaction in block.transactions { - let cached_ffi_transaction = Arc::new(CachedFfiTransaction::new(transaction.clone())); + let cached_ffi_transaction = + Arc::new(CachedFfiTransaction::new(transaction.clone(), Vec::new())); let tx_sigop_count = cached_ffi_transaction.legacy_sigop_count(); assert_eq!(tx_sigop_count, Ok(1)); legacy_sigop_count += tx_sigop_count.expect("unexpected invalid sigop count"); @@ -668,7 +670,8 @@ fn legacy_sigops_count_for_historic_blocks() { .zcash_deserialize_into() .expect("block test vector is valid"); for transaction in block.transactions { - let cached_ffi_transaction = Arc::new(CachedFfiTransaction::new(transaction.clone())); + let cached_ffi_transaction = + Arc::new(CachedFfiTransaction::new(transaction.clone(), Vec::new())); legacy_sigop_count += cached_ffi_transaction .legacy_sigop_count() .expect("unexpected invalid sigop count"); diff --git a/zebra-consensus/src/chain.rs b/zebra-consensus/src/chain.rs index 937fc1464..675da08f8 100644 --- a/zebra-consensus/src/chain.rs +++ b/zebra-consensus/src/chain.rs @@ -229,8 +229,8 @@ where // transaction verification - let script = script::Verifier::new(state_service.clone()); - let transaction = transaction::Verifier::new(network, script); + let script = script::Verifier::new(); + let transaction = transaction::Verifier::new(network, state_service.clone(), script); let transaction = Buffer::new(BoxService::new(transaction), VERIFIER_BUFFER_BOUND); // block verification diff --git a/zebra-consensus/src/script.rs b/zebra-consensus/src/script.rs index efbfbbe4e..d0e6efa07 100644 --- a/zebra-consensus/src/script.rs +++ b/zebra-consensus/src/script.rs @@ -1,6 +1,5 @@ -use std::{collections::HashMap, future::Future, pin::Pin, sync::Arc}; +use std::{future::Future, pin::Pin, sync::Arc}; -use tower::timeout::Timeout; use tracing::Instrument; use zebra_chain::{parameters::NetworkUpgrade, transparent}; @@ -8,20 +7,6 @@ use zebra_script::CachedFfiTransaction; use crate::BoxError; -/// A timeout applied to UTXO lookup requests. -/// -/// The exact value is non-essential, but this should be long enough to allow -/// out-of-order verification of blocks (UTXOs are not required to be ready -/// immediately) while being short enough to: -/// * prune blocks that are too far in the future to be worth keeping in the -/// queue, -/// * fail blocks that reference invalid UTXOs, and -/// * fail blocks that reference UTXOs from blocks that have temporarily failed -/// to download, because a peer sent Zebra a bad list of block hashes. (The -/// UTXO verification failure will restart the sync, and re-download the -/// chain in the correct order.) -const UTXO_LOOKUP_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3 * 60); - /// Asynchronous script verification. /// /// The verifier asynchronously requests the UTXO a transaction attempts @@ -33,16 +18,12 @@ const UTXO_LOOKUP_TIMEOUT: std::time::Duration = std::time::Duration::from_secs( /// The asynchronous script verification design is documented in [RFC4]. /// /// [RFC4]: https://zebra.zfnd.org/dev/rfcs/0004-asynchronous-script-verification.html -#[derive(Debug, Clone)] -pub struct Verifier { - state: Timeout, -} +#[derive(Debug, Clone, Default)] +pub struct Verifier {} -impl Verifier { - pub fn new(state: ZS) -> Self { - Self { - state: Timeout::new(state, UTXO_LOOKUP_TIMEOUT), - } +impl Verifier { + pub fn new() -> Self { + Self {} } } @@ -55,10 +36,6 @@ pub struct Request { /// /// Coinbase inputs are rejected by the script verifier, because they do not spend a UTXO. pub input_index: usize, - /// A set of additional UTXOs known in the context of this verification request. - /// - /// This allows specifying additional UTXOs that are not already known to the chain state. - pub known_utxos: Arc>, /// The network upgrade active in the context of this verification request. /// /// Because the consensus branch ID changes with each network upgrade, @@ -66,36 +43,17 @@ pub struct Request { pub upgrade: NetworkUpgrade, } -/// A script verification response. -/// -/// A successful response returns the known or looked-up UTXO for the transaction input. -/// This allows the transaction verifier to calculate the value of the transparent input. -#[derive(Debug)] -pub struct Response { - /// The `OutPoint` for the UTXO spent by the verified transparent input. - pub spent_outpoint: transparent::OutPoint, - - /// The UTXO spent by the verified transparent input. - /// - /// The value of this UTXO is the value of the input. - pub spent_utxo: transparent::Utxo, -} - -impl tower::Service for Verifier -where - ZS: tower::Service, - ZS::Future: Send + 'static, -{ - type Response = Response; +impl tower::Service for Verifier { + type Response = (); type Error = BoxError; type Future = Pin> + Send + 'static>>; fn poll_ready( &mut self, - cx: &mut std::task::Context<'_>, + _cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { - self.state.poll_ready(cx) + std::task::Poll::Ready(Ok(())) } fn call(&mut self, req: Request) -> Self::Future { @@ -104,7 +62,6 @@ where let Request { cached_ffi_transaction, input_index, - known_utxos, upgrade, } = req; let input = &cached_ffi_transaction.inputs()[input_index]; @@ -118,29 +75,12 @@ where // Avoid calling the state service if the utxo is already known let span = tracing::trace_span!("script", ?outpoint); - let query = - span.in_scope(|| self.state.call(zebra_state::Request::AwaitUtxo(outpoint))); async move { - tracing::trace!("awaiting outpoint lookup"); - let utxo = if let Some(output) = known_utxos.get(&outpoint) { - tracing::trace!("UXTO in known_utxos, discarding query"); - output.utxo.clone() - } else if let zebra_state::Response::Utxo(utxo) = query.await? { - utxo - } else { - unreachable!("AwaitUtxo always responds with Utxo") - }; - tracing::trace!(?utxo, "got UTXO"); - - cached_ffi_transaction - .is_valid(branch_id, (input_index as u32, utxo.clone().output))?; + cached_ffi_transaction.is_valid(branch_id, input_index)?; tracing::trace!("script verification succeeded"); - Ok(Response { - spent_outpoint: outpoint, - spent_utxo: utxo, - }) + Ok(()) } .instrument(span) .boxed() diff --git a/zebra-consensus/src/transaction.rs b/zebra-consensus/src/transaction.rs index 635b7601f..46b3b8ee4 100644 --- a/zebra-consensus/src/transaction.rs +++ b/zebra-consensus/src/transaction.rs @@ -15,8 +15,7 @@ use futures::{ stream::{FuturesUnordered, StreamExt}, FutureExt, TryFutureExt, }; -use tokio::sync::mpsc; -use tower::{Service, ServiceExt}; +use tower::{timeout::Timeout, Service, ServiceExt}; use tracing::Instrument; use zebra_chain::{ @@ -28,7 +27,7 @@ use zebra_chain::{ transaction::{ self, HashType, SigHash, Transaction, UnminedTx, UnminedTxId, VerifiedUnminedTx, }, - transparent, + transparent::{self, OrderedUtxo}, }; use zebra_script::CachedFfiTransaction; @@ -40,6 +39,20 @@ pub mod check; #[cfg(test)] mod tests; +/// A timeout applied to UTXO lookup requests. +/// +/// The exact value is non-essential, but this should be long enough to allow +/// out-of-order verification of blocks (UTXOs are not required to be ready +/// immediately) while being short enough to: +/// * prune blocks that are too far in the future to be worth keeping in the +/// queue, +/// * fail blocks that reference invalid UTXOs, and +/// * fail blocks that reference UTXOs from blocks that have temporarily failed +/// to download, because a peer sent Zebra a bad list of block hashes. (The +/// UTXO verification failure will restart the sync, and re-download the +/// chain in the correct order.) +const UTXO_LOOKUP_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3 * 60); + /// Asynchronous transaction verification. /// /// # Correctness @@ -50,7 +63,8 @@ mod tests; #[derive(Debug, Clone)] pub struct Verifier { network: Network, - script_verifier: script::Verifier, + state: Timeout, + script_verifier: script::Verifier, } impl Verifier @@ -59,9 +73,10 @@ where ZS::Future: Send + 'static, { /// Create a new transaction verifier. - pub fn new(network: Network, script_verifier: script::Verifier) -> Self { + pub fn new(network: Network, state: ZS, script_verifier: script::Verifier) -> Self { Self { network, + state: Timeout::new(state, UTXO_LOOKUP_TIMEOUT), script_verifier, } } @@ -281,6 +296,7 @@ where fn call(&mut self, req: Request) -> Self::Future { let script_verifier = self.script_verifier.clone(); let network = self.network; + let state = self.state.clone(); let tx = req.transaction(); let tx_id = req.tx_id(); @@ -289,10 +305,6 @@ where async move { tracing::trace!(?req); - // the size of this channel is bounded by the maximum number of inputs in a transaction - // (approximately 50,000 for a 2 MB transaction) - let (utxo_sender, mut utxo_receiver) = mpsc::unbounded_channel(); - // Do basic checks first if let Some(block_time) = req.block_time() { check::lock_time_has_passed(&tx, req.height(), block_time)?; @@ -338,7 +350,12 @@ where // // https://zips.z.cash/zip-0213#specification - let cached_ffi_transaction = Arc::new(CachedFfiTransaction::new(tx.clone())); + // Load spent UTXOs from state. + let (spent_utxos, spent_outputs) = + Self::spent_utxos(tx.clone(), req.known_utxos(), state).await?; + + let cached_ffi_transaction = + Arc::new(CachedFfiTransaction::new(tx.clone(), spent_outputs)); let async_checks = match tx.as_ref() { Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => { tracing::debug!(?tx, "got transaction with wrong version"); @@ -353,7 +370,6 @@ where network, script_verifier, cached_ffi_transaction.clone(), - utxo_sender, joinsplit_data, sapling_shielded_data, )?, @@ -366,7 +382,6 @@ where network, script_verifier, cached_ffi_transaction.clone(), - utxo_sender, sapling_shielded_data, orchard_shielded_data, )?, @@ -376,11 +391,6 @@ where // Zebra will timeout here, waiting for the async checks. async_checks.check().await?; - let mut spent_utxos = HashMap::new(); - while let Some(script_rsp) = utxo_receiver.recv().await { - spent_utxos.insert(script_rsp.spent_outpoint, script_rsp.spent_utxo); - } - // Get the `value_balance` to calculate the transaction fee. let value_balance = tx.value_balance(&spent_utxos); @@ -425,6 +435,58 @@ where ZS: Service + Send + Clone + 'static, ZS::Future: Send + 'static, { + /// Get the UTXOs that are being spent by the given transaction. + /// + /// `known_utxos` are additional UTXOs known at the time of validation (i.e. + /// from previous transactions in the block). + /// + /// Returns a tuple with a OutPoint -> Utxo map, and a vector of Outputs + /// in the same order as the matching inputs in the transaction. + async fn spent_utxos( + tx: Arc, + known_utxos: Arc>, + state: Timeout, + ) -> Result< + ( + HashMap, + Vec, + ), + TransactionError, + > { + let inputs = tx.inputs(); + let mut spent_utxos = HashMap::new(); + let mut spent_outputs = Vec::new(); + for input in inputs { + match input { + transparent::Input::PrevOut { + outpoint, + unlock_script: _, + sequence: _, + } => { + tracing::trace!("awaiting outpoint lookup"); + let utxo = if let Some(output) = known_utxos.get(outpoint) { + tracing::trace!("UXTO in known_utxos, discarding query"); + output.utxo.clone() + } else { + let query = state + .clone() + .oneshot(zebra_state::Request::AwaitUtxo(*outpoint)); + if let zebra_state::Response::Utxo(utxo) = query.await? { + utxo + } else { + unreachable!("AwaitUtxo always responds with Utxo") + } + }; + tracing::trace!(?utxo, "got UTXO"); + spent_outputs.push(utxo.output.clone()); + spent_utxos.insert(*outpoint, utxo); + } + transparent::Input::Coinbase { .. } => continue, + } + } + Ok((spent_utxos, spent_outputs)) + } + /// Verify a V4 transaction. /// /// Returns a set of asynchronous checks that must all succeed for the transaction to be @@ -446,9 +508,8 @@ where fn verify_v4_transaction( request: &Request, network: Network, - script_verifier: script::Verifier, + script_verifier: script::Verifier, cached_ffi_transaction: Arc, - utxo_sender: mpsc::UnboundedSender, joinsplit_data: &Option>, sapling_shielded_data: &Option>, ) -> Result { @@ -457,14 +518,18 @@ where Self::verify_v4_transaction_network_upgrade(&tx, upgrade)?; - let shielded_sighash = tx.sighash(upgrade, HashType::ALL, None); + let shielded_sighash = tx.sighash( + upgrade, + HashType::ALL, + cached_ffi_transaction.all_previous_outputs(), + None, + ); Ok(Self::verify_transparent_inputs_and_outputs( request, network, script_verifier, cached_ffi_transaction, - utxo_sender, )? .and(Self::verify_sprout_shielded_data( joinsplit_data, @@ -528,9 +593,8 @@ where fn verify_v5_transaction( request: &Request, network: Network, - script_verifier: script::Verifier, + script_verifier: script::Verifier, cached_ffi_transaction: Arc, - utxo_sender: mpsc::UnboundedSender, sapling_shielded_data: &Option>, orchard_shielded_data: &Option, ) -> Result { @@ -539,14 +603,18 @@ where Self::verify_v5_transaction_network_upgrade(&transaction, upgrade)?; - let shielded_sighash = transaction.sighash(upgrade, HashType::ALL, None); + let shielded_sighash = transaction.sighash( + upgrade, + HashType::ALL, + cached_ffi_transaction.all_previous_outputs(), + None, + ); Ok(Self::verify_transparent_inputs_and_outputs( request, network, script_verifier, cached_ffi_transaction, - utxo_sender, )? .and(Self::verify_sapling_shielded_data( sapling_shielded_data, @@ -597,9 +665,8 @@ where fn verify_transparent_inputs_and_outputs( request: &Request, network: Network, - script_verifier: script::Verifier, + script_verifier: script::Verifier, cached_ffi_transaction: Arc, - utxo_sender: mpsc::UnboundedSender, ) -> Result { let transaction = request.transaction(); @@ -611,24 +678,18 @@ where // feed all of the inputs to the script verifier // the script_verifier also checks transparent sighashes, using its own implementation let inputs = transaction.inputs(); - let known_utxos = request.known_utxos(); let upgrade = request.upgrade(network); let script_checks = (0..inputs.len()) .into_iter() .map(move |input_index| { - let utxo_sender = utxo_sender.clone(); - let request = script::Request { upgrade, - known_utxos: known_utxos.clone(), cached_ffi_transaction: cached_ffi_transaction.clone(), input_index, }; - script_verifier.clone().oneshot(request).map_ok(move |rsp| { - utxo_sender.send(rsp).expect("receiver is not dropped"); - }) + script_verifier.clone().oneshot(request).map_ok(|_r| {}) }) .collect(); diff --git a/zebra-consensus/src/transaction/tests.rs b/zebra-consensus/src/transaction/tests.rs index f14ebabd0..aed7d682b 100644 --- a/zebra-consensus/src/transaction/tests.rs +++ b/zebra-consensus/src/transaction/tests.rs @@ -249,8 +249,8 @@ async fn v5_transaction_is_rejected_before_nu5_activation() { for (network, blocks) in networks { let state_service = service_fn(|_| async { unreachable!("Service should not be called") }); - let script_verifier = script::Verifier::new(state_service); - let verifier = Verifier::new(network, script_verifier); + let script_verifier = script::Verifier::new(); + let verifier = Verifier::new(network, state_service, script_verifier); let transaction = fake_v5_transactions_for_network(network, blocks) .rev() @@ -303,8 +303,8 @@ async fn v5_transaction_is_accepted_after_nu5_activation_for_network(network: Ne }; let state_service = service_fn(|_| async { unreachable!("Service should not be called") }); - let script_verifier = script::Verifier::new(state_service); - let verifier = Verifier::new(network, script_verifier); + let script_verifier = script::Verifier::new(); + let verifier = Verifier::new(network, state_service, script_verifier); let transaction = fake_v5_transactions_for_network(network, blocks) .rev() @@ -365,8 +365,8 @@ async fn v4_transaction_with_transparent_transfer_is_accepted() { let state_service = service_fn(|_| async { unreachable!("State service should not be called") }); - let script_verifier = script::Verifier::new(state_service); - let verifier = Verifier::new(network, script_verifier); + let script_verifier = script::Verifier::new(); + let verifier = Verifier::new(network, state_service, script_verifier); let result = verifier .oneshot(Request::Block { @@ -412,8 +412,8 @@ async fn v4_coinbase_transaction_is_accepted() { let state_service = service_fn(|_| async { unreachable!("State service should not be called") }); - let script_verifier = script::Verifier::new(state_service); - let verifier = Verifier::new(network, script_verifier); + let script_verifier = script::Verifier::new(); + let verifier = Verifier::new(network, state_service, script_verifier); let result = verifier .oneshot(Request::Block { @@ -463,8 +463,8 @@ async fn v4_transaction_with_transparent_transfer_is_rejected_by_the_script() { let state_service = service_fn(|_| async { unreachable!("State service should not be called") }); - let script_verifier = script::Verifier::new(state_service); - let verifier = Verifier::new(network, script_verifier); + let script_verifier = script::Verifier::new(); + let verifier = Verifier::new(network, state_service, script_verifier); let result = verifier .oneshot(Request::Block { @@ -514,8 +514,8 @@ async fn v4_transaction_with_conflicting_transparent_spend_is_rejected() { let state_service = service_fn(|_| async { unreachable!("State service should not be called") }); - let script_verifier = script::Verifier::new(state_service); - let verifier = Verifier::new(network, script_verifier); + let script_verifier = script::Verifier::new(); + let verifier = Verifier::new(network, state_service, script_verifier); let result = verifier .oneshot(Request::Block { @@ -569,7 +569,7 @@ fn v4_transaction_with_conflicting_sprout_nullifier_inside_joinsplit_is_rejected }; // Sign the transaction - let sighash = transaction.sighash(network_upgrade, HashType::ALL, None); + let sighash = transaction.sighash(network_upgrade, HashType::ALL, &Vec::new(), None); match &mut transaction { Transaction::V4 { @@ -581,8 +581,8 @@ fn v4_transaction_with_conflicting_sprout_nullifier_inside_joinsplit_is_rejected let state_service = service_fn(|_| async { unreachable!("State service should not be called") }); - let script_verifier = script::Verifier::new(state_service); - let verifier = Verifier::new(network, script_verifier); + let script_verifier = script::Verifier::new(); + let verifier = Verifier::new(network, state_service, script_verifier); let result = verifier .oneshot(Request::Block { @@ -641,7 +641,7 @@ fn v4_transaction_with_conflicting_sprout_nullifier_across_joinsplits_is_rejecte }; // Sign the transaction - let sighash = transaction.sighash(network_upgrade, HashType::ALL, None); + let sighash = transaction.sighash(network_upgrade, HashType::ALL, &Vec::new(), None); match &mut transaction { Transaction::V4 { @@ -653,8 +653,8 @@ fn v4_transaction_with_conflicting_sprout_nullifier_across_joinsplits_is_rejecte let state_service = service_fn(|_| async { unreachable!("State service should not be called") }); - let script_verifier = script::Verifier::new(state_service); - let verifier = Verifier::new(network, script_verifier); + let script_verifier = script::Verifier::new(); + let verifier = Verifier::new(network, state_service, script_verifier); let result = verifier .oneshot(Request::Block { @@ -708,8 +708,8 @@ async fn v5_transaction_with_transparent_transfer_is_accepted() { let state_service = service_fn(|_| async { unreachable!("State service should not be called") }); - let script_verifier = script::Verifier::new(state_service); - let verifier = Verifier::new(network, script_verifier); + let script_verifier = script::Verifier::new(); + let verifier = Verifier::new(network, state_service, script_verifier); let result = verifier .oneshot(Request::Block { @@ -758,8 +758,8 @@ async fn v5_coinbase_transaction_is_accepted() { let state_service = service_fn(|_| async { unreachable!("State service should not be called") }); - let script_verifier = script::Verifier::new(state_service); - let verifier = Verifier::new(network, script_verifier); + let script_verifier = script::Verifier::new(); + let verifier = Verifier::new(network, state_service, script_verifier); let result = verifier .oneshot(Request::Block { @@ -811,8 +811,8 @@ async fn v5_transaction_with_transparent_transfer_is_rejected_by_the_script() { let state_service = service_fn(|_| async { unreachable!("State service should not be called") }); - let script_verifier = script::Verifier::new(state_service); - let verifier = Verifier::new(network, script_verifier); + let script_verifier = script::Verifier::new(); + let verifier = Verifier::new(network, state_service, script_verifier); let result = verifier .oneshot(Request::Block { @@ -864,8 +864,8 @@ async fn v5_transaction_with_conflicting_transparent_spend_is_rejected() { let state_service = service_fn(|_| async { unreachable!("State service should not be called") }); - let script_verifier = script::Verifier::new(state_service); - let verifier = Verifier::new(network, script_verifier); + let script_verifier = script::Verifier::new(); + let verifier = Verifier::new(network, state_service, script_verifier); let result = verifier .oneshot(Request::Block { @@ -910,8 +910,8 @@ fn v4_with_signed_sprout_transfer_is_accepted() { // Initialize the verifier let state_service = service_fn(|_| async { unreachable!("State service should not be called") }); - let script_verifier = script::Verifier::new(state_service); - let verifier = Verifier::new(network, script_verifier); + let script_verifier = script::Verifier::new(); + let verifier = Verifier::new(network, state_service, script_verifier); // Test the transaction verifier let result = verifier @@ -984,8 +984,8 @@ async fn v4_with_joinsplit_is_rejected_for_modification( // Initialize the verifier let state_service = service_fn(|_| async { unreachable!("State service should not be called") }); - let script_verifier = script::Verifier::new(state_service); - let verifier = Verifier::new(network, script_verifier); + let script_verifier = script::Verifier::new(); + let verifier = Verifier::new(network, state_service, script_verifier); // Test the transaction verifier let result = verifier @@ -1022,8 +1022,8 @@ fn v4_with_sapling_spends() { // Initialize the verifier let state_service = service_fn(|_| async { unreachable!("State service should not be called") }); - let script_verifier = script::Verifier::new(state_service); - let verifier = Verifier::new(network, script_verifier); + let script_verifier = script::Verifier::new(); + let verifier = Verifier::new(network, state_service, script_verifier); // Test the transaction verifier let result = verifier @@ -1067,8 +1067,8 @@ fn v4_with_duplicate_sapling_spends() { // Initialize the verifier let state_service = service_fn(|_| async { unreachable!("State service should not be called") }); - let script_verifier = script::Verifier::new(state_service); - let verifier = Verifier::new(network, script_verifier); + let script_verifier = script::Verifier::new(); + let verifier = Verifier::new(network, state_service, script_verifier); // Test the transaction verifier let result = verifier @@ -1114,8 +1114,8 @@ fn v4_with_sapling_outputs_and_no_spends() { // Initialize the verifier let state_service = service_fn(|_| async { unreachable!("State service should not be called") }); - let script_verifier = script::Verifier::new(state_service); - let verifier = Verifier::new(network, script_verifier); + let script_verifier = script::Verifier::new(); + let verifier = Verifier::new(network, state_service, script_verifier); // Test the transaction verifier let result = verifier @@ -1162,8 +1162,8 @@ fn v5_with_sapling_spends() { // Initialize the verifier let state_service = service_fn(|_| async { unreachable!("State service should not be called") }); - let script_verifier = script::Verifier::new(state_service); - let verifier = Verifier::new(network, script_verifier); + let script_verifier = script::Verifier::new(); + let verifier = Verifier::new(network, state_service, script_verifier); // Test the transaction verifier let result = verifier @@ -1210,8 +1210,8 @@ fn v5_with_duplicate_sapling_spends() { // Initialize the verifier let state_service = service_fn(|_| async { unreachable!("State service should not be called") }); - let script_verifier = script::Verifier::new(state_service); - let verifier = Verifier::new(network, script_verifier); + let script_verifier = script::Verifier::new(); + let verifier = Verifier::new(network, state_service, script_verifier); // Test the transaction verifier let result = verifier @@ -1274,8 +1274,8 @@ fn v5_with_duplicate_orchard_action() { // Initialize the verifier let state_service = service_fn(|_| async { unreachable!("State service should not be called") }); - let script_verifier = script::Verifier::new(state_service); - let verifier = Verifier::new(network, script_verifier); + let script_verifier = script::Verifier::new(); + let verifier = Verifier::new(network, state_service, script_verifier); // Test the transaction verifier let result = verifier diff --git a/zebra-consensus/src/transaction/tests/prop.rs b/zebra-consensus/src/transaction/tests/prop.rs index 06a4a4466..f9ca93e57 100644 --- a/zebra-consensus/src/transaction/tests/prop.rs +++ b/zebra-consensus/src/transaction/tests/prop.rs @@ -441,8 +441,8 @@ fn validate( // Initialize the verifier let state_service = tower::service_fn(|_| async { unreachable!("State service should not be called") }); - let script_verifier = script::Verifier::new(state_service); - let verifier = transaction::Verifier::new(network, script_verifier); + let script_verifier = script::Verifier::new(); + let verifier = transaction::Verifier::new(network, state_service, script_verifier); // Test the transaction verifier verifier diff --git a/zebra-script/src/lib.rs b/zebra-script/src/lib.rs index ab4208f69..86e96d265 100644 --- a/zebra-script/src/lib.rs +++ b/zebra-script/src/lib.rs @@ -56,15 +56,23 @@ impl From for Error { } } -/// A preprocessed Transction which can be used to verify scripts within said +/// A preprocessed Transaction which can be used to verify scripts within said /// Transaction. #[derive(Debug)] pub struct CachedFfiTransaction { /// The deserialized Zebra transaction. /// - /// This field is private so that `transaction` and `precomputed` always match. + /// This field is private so that `transaction`, `all_previous_outputs`, and `precomputed` always match. transaction: Arc, + /// The outputs from previous transactions that match each input in the transaction + /// being verified. + /// + /// SAFETY: this field must be private, + /// and `CachedFfiTransaction::new` must be the only method that modifies it, + /// so that it is [`Send`], [`Sync`], consistent with `transaction` and `precomputed`. + all_previous_outputs: Vec, + /// The deserialized `zcash_script` transaction, as a C++ object. /// /// SAFETY: this field must be private, @@ -74,8 +82,13 @@ pub struct CachedFfiTransaction { } impl CachedFfiTransaction { - /// Construct a `PrecomputedTransaction` from a `Transaction`. - pub fn new(transaction: Arc) -> Self { + /// Construct a `PrecomputedTransaction` from a `Transaction` and the outputs + /// from previous transactions that match each input in the transaction + /// being verified. + pub fn new( + transaction: Arc, + all_previous_outputs: Vec, + ) -> Self { let tx_to = transaction .zcash_serialize_to_vec() .expect("serialization into a vec is infallible"); @@ -87,9 +100,26 @@ impl CachedFfiTransaction { .expect("serialized transaction lengths are much less than u32::MAX"); let mut err = 0; - // SAFETY: the `tx_to_*` fields are created from a valid Rust `Vec`. + let serialized_all_previous_outputs = all_previous_outputs + .zcash_serialize_to_vec() + .expect("serialization into a vec is infallible"); + // TODO: pass to zcash_script after API update + let _all_previous_outputs_ptr = serialized_all_previous_outputs.as_ptr(); + let _all_previous_outputs_len: u32 = serialized_all_previous_outputs + .len() + .try_into() + .expect("serialized transaction lengths are much less than u32::MAX"); + + // SAFETY: + // the `tx_to_*` fields are created from a valid Rust `Vec` + // the `all_previous_outputs_*` fields are created from a valid Rust `Vec` let precomputed = unsafe { - zcash_script::zcash_script_new_precomputed_tx(tx_to_ptr, tx_to_len, &mut err) + zcash_script::zcash_script_new_precomputed_tx( + tx_to_ptr, tx_to_len, + // all_previous_outputs_ptr, + // all_previous_outputs_len, + &mut err, + ) }; // SAFETY: the safety of other methods depends on `precomputed` being valid and not NULL. assert!( @@ -101,6 +131,7 @@ impl CachedFfiTransaction { Self { transaction, + all_previous_outputs, // SAFETY: `precomputed` must not be modified after initialisation, // so that it is `Send` and `Sync`. precomputed, @@ -112,19 +143,21 @@ impl CachedFfiTransaction { self.transaction.inputs() } - /// Verify a script within a transaction given the corresponding - /// `transparent::Output` it is spending and the `ConsensusBranchId` of the block - /// containing the transaction. - /// - /// # Details - /// - /// The `input_index` corresponds to the index of the `TransparentInput` which in - /// `transaction` used to identify the `previous_output`. - pub fn is_valid( - &self, - branch_id: ConsensusBranchId, - (input_index, previous_output): (u32, transparent::Output), - ) -> Result<(), Error> { + /// Returns the outputs from previous transactions that match each input in the transaction + /// being verified. + pub fn all_previous_outputs(&self) -> &Vec { + &self.all_previous_outputs + } + + /// Verify if the script in the input at `input_index` of a transaction correctly + /// spends the matching `transparent::Output` it refers to, with the `ConsensusBranchId` + /// of the block containing the transaction. + pub fn is_valid(&self, branch_id: ConsensusBranchId, input_index: usize) -> Result<(), Error> { + let previous_output = self + .all_previous_outputs + .get(input_index) + .ok_or(Error::TxIndex)? + .clone(); let transparent::Output { value, lock_script } = previous_output; let script_pub_key: &[u8] = lock_script.as_raw_bytes(); @@ -223,6 +256,12 @@ impl CachedFfiTransaction { // The function `zcash_script:zcash_script_legacy_sigop_count_precomputed` only reads // from the precomputed context. Currently, these reads happen after all the concurrent // async checks have finished. +// +// Since we're manually marking it as `Send` and `Sync`, we must ensure that +// other fields in the struct are also `Send` and `Sync`. This applies to +// `all_previous_outputs`, which are both. +// +// TODO: create a wrapper for `precomputed` and only make it implement Send/Sync (#3436) unsafe impl Send for CachedFfiTransaction {} unsafe impl Sync for CachedFfiTransaction {} @@ -267,8 +306,9 @@ mod tests { .branch_id() .expect("Blossom has a ConsensusBranchId"); - let verifier = super::CachedFfiTransaction::new(transaction); - verifier.is_valid(branch_id, (input_index, output))?; + let previous_output = vec![output]; + let verifier = super::CachedFfiTransaction::new(transaction, previous_output); + verifier.is_valid(branch_id, input_index)?; Ok(()) } @@ -280,7 +320,7 @@ mod tests { let transaction = SCRIPT_TX.zcash_deserialize_into::>()?; - let cached_tx = super::CachedFfiTransaction::new(transaction); + let cached_tx = super::CachedFfiTransaction::new(transaction, Vec::new()); assert_eq!(cached_tx.legacy_sigop_count()?, 1); Ok(()) @@ -303,10 +343,8 @@ mod tests { .branch_id() .expect("Blossom has a ConsensusBranchId"); - let verifier = super::CachedFfiTransaction::new(transaction); - verifier - .is_valid(branch_id, (input_index, output)) - .unwrap_err(); + let verifier = super::CachedFfiTransaction::new(transaction, vec![output]); + verifier.is_valid(branch_id, input_index).unwrap_err(); Ok(()) } @@ -318,27 +356,22 @@ mod tests { let coin = u64::pow(10, 8); let transaction = SCRIPT_TX.zcash_deserialize_into::>()?; + let amount = 212 * coin; + let output = transparent::Output { + value: amount.try_into()?, + lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()), + }; - let verifier = super::CachedFfiTransaction::new(transaction); + let verifier = super::CachedFfiTransaction::new(transaction, vec![output]); let input_index = 0; let branch_id = Blossom .branch_id() .expect("Blossom has a ConsensusBranchId"); - let amount = 212 * coin; - let output = transparent::Output { - value: amount.try_into()?, - lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()), - }; - verifier.is_valid(branch_id, (input_index, output))?; + verifier.is_valid(branch_id, input_index)?; - let amount = 212 * coin; - let output = transparent::Output { - value: amount.try_into()?, - lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()), - }; - verifier.is_valid(branch_id, (input_index, output))?; + verifier.is_valid(branch_id, input_index)?; Ok(()) } @@ -348,31 +381,24 @@ mod tests { zebra_test::init(); let coin = u64::pow(10, 8); + let amount = 212 * coin; + let output = transparent::Output { + value: amount.try_into()?, + lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()), + }; let transaction = SCRIPT_TX.zcash_deserialize_into::>()?; - let verifier = super::CachedFfiTransaction::new(transaction); + let verifier = super::CachedFfiTransaction::new(transaction, vec![output]); let input_index = 0; let branch_id = Blossom .branch_id() .expect("Blossom has a ConsensusBranchId"); - let amount = 212 * coin; - let output = transparent::Output { - value: amount.try_into()?, - lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()), - }; - verifier.is_valid(branch_id, (input_index, output))?; + verifier.is_valid(branch_id, input_index)?; - let amount = 211 * coin; - let output = transparent::Output { - value: amount.try_into()?, - lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()), - }; - verifier - .is_valid(branch_id, (input_index, output)) - .unwrap_err(); + verifier.is_valid(branch_id, input_index + 1).unwrap_err(); Ok(()) } @@ -382,31 +408,24 @@ mod tests { zebra_test::init(); let coin = u64::pow(10, 8); + let amount = 212 * coin; + let output = transparent::Output { + value: amount.try_into()?, + lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()), + }; let transaction = SCRIPT_TX.zcash_deserialize_into::>()?; - let verifier = super::CachedFfiTransaction::new(transaction); + let verifier = super::CachedFfiTransaction::new(transaction, vec![output]); let input_index = 0; let branch_id = Blossom .branch_id() .expect("Blossom has a ConsensusBranchId"); - let amount = 211 * coin; - let output = transparent::Output { - value: amount.try_into()?, - lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()), - }; - verifier - .is_valid(branch_id, (input_index, output)) - .unwrap_err(); + verifier.is_valid(branch_id, input_index + 1).unwrap_err(); - let amount = 212 * coin; - let output = transparent::Output { - value: amount.try_into()?, - lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()), - }; - verifier.is_valid(branch_id, (input_index, output))?; + verifier.is_valid(branch_id, input_index)?; Ok(()) } @@ -416,33 +435,24 @@ mod tests { zebra_test::init(); let coin = u64::pow(10, 8); + let amount = 212 * coin; + let output = transparent::Output { + value: amount.try_into()?, + lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()), + }; let transaction = SCRIPT_TX.zcash_deserialize_into::>()?; - let verifier = super::CachedFfiTransaction::new(transaction); + let verifier = super::CachedFfiTransaction::new(transaction, vec![output]); let input_index = 0; let branch_id = Blossom .branch_id() .expect("Blossom has a ConsensusBranchId"); - let amount = 211 * coin; - let output = transparent::Output { - value: amount.try_into()?, - lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()), - }; - verifier - .is_valid(branch_id, (input_index, output)) - .unwrap_err(); + verifier.is_valid(branch_id, input_index + 1).unwrap_err(); - let amount = 210 * coin; - let output = transparent::Output { - value: amount.try_into()?, - lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()), - }; - verifier - .is_valid(branch_id, (input_index, output)) - .unwrap_err(); + verifier.is_valid(branch_id, input_index + 1).unwrap_err(); Ok(()) }