Skip to content

Commit

Permalink
Send looked up UTXOs to the transaction verifier (#2849)
Browse files Browse the repository at this point in the history
* Send spent UTXOs from the script verifier to the transaction verifier

* Add temporary assertions for testing spent UTXO sending

Co-authored-by: Conrado Gouvea <conrado@zfnd.org>
Co-authored-by: Marek <mail@marek.onl>
  • Loading branch information
3 people authored Oct 12, 2021
1 parent 31e7a21 commit 5d997e9
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 38 deletions.
24 changes: 21 additions & 3 deletions zebra-consensus/src/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ZS> tower::Service<Request> for Verifier<ZS>
where
ZS: tower::Service<zebra_state::Request, Response = zebra_state::Response, Error = BoxError>,
ZS::Future: Send + 'static,
{
type Response = ();
type Response = Response;
type Error = BoxError;
type Future =
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
Expand Down Expand Up @@ -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()
Expand Down
114 changes: 79 additions & 35 deletions zebra-consensus/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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)?;

Expand Down Expand Up @@ -219,6 +224,7 @@ where
network,
script_verifier,
inputs,
utxo_sender,
joinsplit_data,
sapling_shielded_data,
)?,
Expand All @@ -232,13 +238,46 @@ where
network,
script_verifier,
inputs,
utxo_sender,
sapling_shielded_data,
orchard_shielded_data,
)?,
};

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)
Expand Down Expand Up @@ -274,6 +313,7 @@ where
network: Network,
script_verifier: script::Verifier<ZS>,
inputs: &[transparent::Input],
utxo_sender: mpsc::UnboundedSender<script::Response>,
joinsplit_data: &Option<transaction::JoinSplitData<Groth16Proof>>,
sapling_shielded_data: &Option<sapling::ShieldedData<sapling::PerSpendAnchor>>,
) -> Result<AsyncChecks, TransactionError> {
Expand All @@ -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`.
Expand Down Expand Up @@ -356,6 +395,7 @@ where
network: Network,
script_verifier: script::Verifier<ZS>,
inputs: &[transparent::Input],
utxo_sender: mpsc::UnboundedSender<script::Response>,
sapling_shielded_data: &Option<sapling::ShieldedData<sapling::SharedAnchor>>,
orchard_shielded_data: &Option<orchard::ShieldedData>,
) -> Result<AsyncChecks, TransactionError> {
Expand All @@ -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)
Expand Down Expand Up @@ -423,8 +462,9 @@ where
fn verify_transparent_inputs_and_outputs(
request: &Request,
network: Network,
inputs: &[transparent::Input],
script_verifier: script::Verifier<ZS>,
inputs: &[transparent::Input],
utxo_sender: mpsc::UnboundedSender<script::Response>,
) -> Result<AsyncChecks, TransactionError> {
let transaction = request.transaction();

Expand All @@ -442,14 +482,18 @@ 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(),
cached_ffi_transaction: cached_ffi_transaction.clone(),
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();

Expand Down

0 comments on commit 5d997e9

Please sign in to comment.