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:
parent
31e7a21721
commit
5d997e9365
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
Loading…
Reference in New Issue