From 5d997e93652b669777489c5f379ffae771c5f983 Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 12 Oct 2021 10:25:20 +1000 Subject: [PATCH] Send looked up UTXOs to the transaction verifier (#2849) * Send spent UTXOs from the script verifier to the transaction verifier * Add temporary assertions for testing spent UTXO sending Co-authored-by: Conrado Gouvea Co-authored-by: Marek --- zebra-consensus/src/script.rs | 24 +++++- zebra-consensus/src/transaction.rs | 114 ++++++++++++++++++++--------- 2 files changed, 100 insertions(+), 38 deletions(-) diff --git a/zebra-consensus/src/script.rs b/zebra-consensus/src/script.rs index 006f4ae3..efbfbbe4 100644 --- a/zebra-consensus/src/script.rs +++ b/zebra-consensus/src/script.rs @@ -66,12 +66,27 @@ 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 = (); + type Response = Response; type Error = BoxError; type Future = Pin> + Send + 'static>>; @@ -119,10 +134,13 @@ where tracing::trace!(?utxo, "got UTXO"); cached_ffi_transaction - .is_valid(branch_id, (input_index as u32, utxo.output))?; + .is_valid(branch_id, (input_index as u32, utxo.clone().output))?; tracing::trace!("script verification succeeded"); - Ok(()) + Ok(Response { + spent_outpoint: outpoint, + spent_utxo: utxo, + }) } .instrument(span) .boxed() diff --git a/zebra-consensus/src/transaction.rs b/zebra-consensus/src/transaction.rs index d73b088e..31242b2d 100644 --- a/zebra-consensus/src/transaction.rs +++ b/zebra-consensus/src/transaction.rs @@ -11,8 +11,9 @@ use std::{ use futures::{ stream::{FuturesUnordered, StreamExt}, - FutureExt, + FutureExt, TryFutureExt, }; +use tokio::sync::mpsc; use tower::{Service, ServiceExt}; use tracing::Instrument; @@ -178,6 +179,10 @@ 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 check::has_inputs_and_outputs(&tx)?; @@ -219,6 +224,7 @@ where network, script_verifier, inputs, + utxo_sender, joinsplit_data, sapling_shielded_data, )?, @@ -232,6 +238,7 @@ where network, script_verifier, inputs, + utxo_sender, sapling_shielded_data, orchard_shielded_data, )?, @@ -239,6 +246,38 @@ where 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); + } + + // temporary assertions for testing ticket #2440 + // + // TODO: use spent_utxos to calculate the transaction fee (#2779) + // and remove these assertions + if tx.has_valid_coinbase_transaction_inputs() { + assert_eq!( + spent_utxos.len(), + 0, + "already checked that coinbase transactions don't spend UTXOs" + ); + } else if spent_utxos.len() < tx.inputs().len() { + // TODO: replace with double-spend check in PR #2843 + return Err(TransactionError::InternalDowncastError(format!( + "transparent double-spend within a transaction: \ + expected {} input UTXOs, got {} unique spent UTXOs", + tx.inputs().len(), + spent_utxos.len() + ))); + } else { + assert_eq!( + spent_utxos.len(), + tx.inputs().len(), + "unexpected excess looked-up spent UTXOs in transaction: \ + expected exactly one UTXO per verified transparent input" + ); + } + Ok(id) } .instrument(span) @@ -274,6 +313,7 @@ where network: Network, script_verifier: script::Verifier, inputs: &[transparent::Input], + utxo_sender: mpsc::UnboundedSender, joinsplit_data: &Option>, sapling_shielded_data: &Option>, ) -> Result { @@ -284,22 +324,21 @@ where let shielded_sighash = tx.sighash(upgrade, HashType::ALL, None); - Ok( - Self::verify_transparent_inputs_and_outputs( - &request, - network, - inputs, - script_verifier, - )? - .and(Self::verify_sprout_shielded_data( - joinsplit_data, - &shielded_sighash, - )) - .and(Self::verify_sapling_shielded_data( - sapling_shielded_data, - &shielded_sighash, - )?), - ) + Ok(Self::verify_transparent_inputs_and_outputs( + &request, + network, + script_verifier, + inputs, + utxo_sender, + )? + .and(Self::verify_sprout_shielded_data( + joinsplit_data, + &shielded_sighash, + )) + .and(Self::verify_sapling_shielded_data( + sapling_shielded_data, + &shielded_sighash, + )?)) } /// Verifies if a V4 `transaction` is supported by `network_upgrade`. @@ -356,6 +395,7 @@ where network: Network, script_verifier: script::Verifier, inputs: &[transparent::Input], + utxo_sender: mpsc::UnboundedSender, sapling_shielded_data: &Option>, orchard_shielded_data: &Option, ) -> Result { @@ -366,22 +406,21 @@ where let shielded_sighash = transaction.sighash(upgrade, HashType::ALL, None); - Ok( - Self::verify_transparent_inputs_and_outputs( - &request, - network, - inputs, - script_verifier, - )? - .and(Self::verify_sapling_shielded_data( - sapling_shielded_data, - &shielded_sighash, - )?) - .and(Self::verify_orchard_shielded_data( - orchard_shielded_data, - &shielded_sighash, - )?), - ) + Ok(Self::verify_transparent_inputs_and_outputs( + &request, + network, + script_verifier, + inputs, + utxo_sender, + )? + .and(Self::verify_sapling_shielded_data( + sapling_shielded_data, + &shielded_sighash, + )?) + .and(Self::verify_orchard_shielded_data( + orchard_shielded_data, + &shielded_sighash, + )?)) // TODO: // - verify orchard shielded pool (ZIP-224) (#2105) @@ -423,8 +462,9 @@ where fn verify_transparent_inputs_and_outputs( request: &Request, network: Network, - inputs: &[transparent::Input], script_verifier: script::Verifier, + inputs: &[transparent::Input], + utxo_sender: mpsc::UnboundedSender, ) -> Result { let transaction = request.transaction(); @@ -442,6 +482,8 @@ where 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(), @@ -449,7 +491,9 @@ where input_index, }; - script_verifier.clone().oneshot(request) + script_verifier.clone().oneshot(request).map_ok(move |rsp| { + utxo_sender.send(rsp).expect("receiver is not dropped"); + }) }) .collect();