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 <conrado@zfnd.org>
Co-authored-by: Marek <mail@marek.onl>
This commit is contained in:
teor 2021-10-12 10:25:20 +10:00 committed by GitHub
parent 31e7a21721
commit 5d997e9365
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 100 additions and 38 deletions

View File

@ -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>>;
@ -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()

View File

@ -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<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> {
@ -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<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> {
@ -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<ZS>,
inputs: &[transparent::Input],
utxo_sender: mpsc::UnboundedSender<script::Response>,
) -> Result<AsyncChecks, TransactionError> {
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();