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 <teor@riseup.net>

* 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 <teor@riseup.net>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
Conrado Gouvea 2022-01-31 12:28:42 -03:00 committed by GitHub
parent 8939ddf3d8
commit f270fd2de6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 367 additions and 303 deletions

View File

@ -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<usize>,
) -> 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

View File

@ -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<usize>,
) -> 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

View File

@ -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<usize>,
}
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<usize>,
) -> 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,
)
}
}

View File

@ -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::<Amount<_>>()?;
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::<Transaction>()?;
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::<Amount<_>>()?;
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::<Transaction>()?;
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::<Transaction>()?;
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::<Transaction>()?;
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(),

View File

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

View File

@ -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

View File

@ -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<ZS> {
state: Timeout<ZS>,
}
#[derive(Debug, Clone, Default)]
pub struct Verifier {}
impl<ZS> Verifier<ZS> {
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<HashMap<transparent::OutPoint, transparent::OrderedUtxo>>,
/// 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<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 = Response;
impl tower::Service<Request> for Verifier {
type Response = ();
type Error = BoxError;
type Future =
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
fn poll_ready(
&mut self,
cx: &mut std::task::Context<'_>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
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()

View File

@ -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<ZS> {
network: Network,
script_verifier: script::Verifier<ZS>,
state: Timeout<ZS>,
script_verifier: script::Verifier,
}
impl<ZS> Verifier<ZS>
@ -59,9 +73,10 @@ where
ZS::Future: Send + 'static,
{
/// Create a new transaction verifier.
pub fn new(network: Network, script_verifier: script::Verifier<ZS>) -> 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<zs::Request, Response = zs::Response, Error = BoxError> + 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<Transaction>,
known_utxos: Arc<HashMap<transparent::OutPoint, OrderedUtxo>>,
state: Timeout<ZS>,
) -> Result<
(
HashMap<transparent::OutPoint, transparent::Utxo>,
Vec<transparent::Output>,
),
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<ZS>,
script_verifier: script::Verifier,
cached_ffi_transaction: Arc<CachedFfiTransaction>,
utxo_sender: mpsc::UnboundedSender<script::Response>,
joinsplit_data: &Option<transaction::JoinSplitData<Groth16Proof>>,
sapling_shielded_data: &Option<sapling::ShieldedData<sapling::PerSpendAnchor>>,
) -> Result<AsyncChecks, TransactionError> {
@ -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<ZS>,
script_verifier: script::Verifier,
cached_ffi_transaction: Arc<CachedFfiTransaction>,
utxo_sender: mpsc::UnboundedSender<script::Response>,
sapling_shielded_data: &Option<sapling::ShieldedData<sapling::SharedAnchor>>,
orchard_shielded_data: &Option<orchard::ShieldedData>,
) -> Result<AsyncChecks, TransactionError> {
@ -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<ZS>,
script_verifier: script::Verifier,
cached_ffi_transaction: Arc<CachedFfiTransaction>,
utxo_sender: mpsc::UnboundedSender<script::Response>,
) -> Result<AsyncChecks, TransactionError> {
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();

View File

@ -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

View File

@ -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

View File

@ -56,15 +56,23 @@ impl From<zcash_script_error_t> 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<Transaction>,
/// 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<transparent::Output>,
/// 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<Transaction>) -> 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<Transaction>,
all_previous_outputs: Vec<transparent::Output>,
) -> 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<transparent::Output> {
&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::<Arc<zebra_chain::transaction::Transaction>>()?;
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::<Arc<zebra_chain::transaction::Transaction>>()?;
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::<Arc<zebra_chain::transaction::Transaction>>()?;
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::<Arc<zebra_chain::transaction::Transaction>>()?;
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::<Arc<zebra_chain::transaction::Transaction>>()?;
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(())
}