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, trans: &Transaction,
hash_type: HashType, hash_type: HashType,
network_upgrade: NetworkUpgrade, network_upgrade: NetworkUpgrade,
input: Option<(&transparent::Output, &transparent::Input, usize)>, all_previous_outputs: &[transparent::Output],
input_index: Option<usize>,
) -> SigHash { ) -> SigHash {
let alt_tx = convert_tx_to_librustzcash(trans, network_upgrade) let alt_tx = convert_tx_to_librustzcash(trans, network_upgrade)
.expect("zcash_primitives and Zebra transaction formats must be compatible"); .expect("zcash_primitives and Zebra transaction formats must be compatible");
let script: zcash_primitives::legacy::Script; let script: zcash_primitives::legacy::Script;
let signable_input = match input { let signable_input = match input_index {
Some((output, _, idx)) => { Some(input_index) => {
let output = all_previous_outputs[input_index].clone();
script = (&output.lock_script).into(); script = (&output.lock_script).into();
zcash_primitives::transaction::sighash::SignableInput::Transparent( zcash_primitives::transaction::sighash::SignableInput::Transparent(
zcash_primitives::transaction::sighash::TransparentInput::new( zcash_primitives::transaction::sighash::TransparentInput::new(
idx, input_index,
&script, &script,
output output
.value .value

View File

@ -191,9 +191,17 @@ impl Transaction {
&self, &self,
network_upgrade: NetworkUpgrade, network_upgrade: NetworkUpgrade,
hash_type: sighash::HashType, hash_type: sighash::HashType,
input: Option<(u32, transparent::Output)>, all_previous_outputs: &[transparent::Output],
input: Option<usize>,
) -> SigHash { ) -> 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 /// Compute the authorizing data commitment of this transaction as specified

View File

@ -43,7 +43,8 @@ pub(super) struct SigHasher<'a> {
trans: &'a Transaction, trans: &'a Transaction,
hash_type: HashType, hash_type: HashType,
network_upgrade: NetworkUpgrade, 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> { impl<'a> SigHasher<'a> {
@ -51,22 +52,15 @@ impl<'a> SigHasher<'a> {
trans: &'a Transaction, trans: &'a Transaction,
hash_type: HashType, hash_type: HashType,
network_upgrade: NetworkUpgrade, network_upgrade: NetworkUpgrade,
input: Option<(u32, transparent::Output)>, all_previous_outputs: &'a [transparent::Output],
input_index: Option<usize>,
) -> Self { ) -> 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 { SigHasher {
trans, trans,
hash_type, hash_type,
network_upgrade, network_upgrade,
input, all_previous_outputs,
input_index,
} }
} }
@ -83,10 +77,12 @@ impl<'a> SigHasher<'a> {
/// Compute a signature hash using librustzcash. /// Compute a signature hash using librustzcash.
fn hash_sighash_librustzcash(&self) -> SigHash { fn hash_sighash_librustzcash(&self) -> SigHash {
let input = self sighash(
.input self.trans,
.as_ref() self.hash_type,
.map(|(output, input, idx)| (output, *input, *idx)); self.network_upgrade,
sighash(self.trans, self.hash_type, self.network_upgrade, input) self.all_previous_outputs,
self.input_index,
)
} }
} }

View File

@ -612,6 +612,7 @@ fn test_vec143_1() -> Result<()> {
&transaction, &transaction,
HashType::ALL, HashType::ALL,
NetworkUpgrade::Overwinter, NetworkUpgrade::Overwinter,
Default::default(),
None, None,
); );
@ -639,12 +640,16 @@ fn test_vec143_2() -> Result<()> {
let value = hex::decode("2f6e04963b4c0100")?.zcash_deserialize_into::<Amount<_>>()?; let value = hex::decode("2f6e04963b4c0100")?.zcash_deserialize_into::<Amount<_>>()?;
let lock_script = Script::new(&hex::decode("53")?); let lock_script = Script::new(&hex::decode("53")?);
let input_ind = 1; let input_ind = 1;
let output = transparent::Output { value, lock_script };
let all_previous_outputs = vec![output.clone(), output];
let hasher = SigHasher::new( let hasher = SigHasher::new(
&transaction, &transaction,
HashType::SINGLE, HashType::SINGLE,
NetworkUpgrade::Overwinter, 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(); let hash = hasher.sighash();
@ -669,7 +674,13 @@ fn test_vec243_1() -> Result<()> {
let transaction = ZIP243_1.zcash_deserialize_into::<Transaction>()?; 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 hash = hasher.sighash();
let expected = "63d18534de5f2d1c9e169b73f9c783718adbef5c8a7d55b5e7a37affa1dd3ff3"; let expected = "63d18534de5f2d1c9e169b73f9c783718adbef5c8a7d55b5e7a37affa1dd3ff3";
@ -687,6 +698,7 @@ fn test_vec243_1() -> Result<()> {
&transaction, &transaction,
HashType::ALL, HashType::ALL,
NetworkUpgrade::Sapling, NetworkUpgrade::Sapling,
&[],
None, None,
); );
let result = hex::encode(alt_sighash); 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 value = hex::decode("adedf02996510200")?.zcash_deserialize_into::<Amount<_>>()?;
let lock_script = Script::new(&[]); let lock_script = Script::new(&[]);
let input_ind = 1; let input_ind = 1;
let output = transparent::Output { value, lock_script };
let all_previous_outputs = vec![output.clone(), output];
let hasher = SigHasher::new( let hasher = SigHasher::new(
&transaction, &transaction,
HashType::NONE, HashType::NONE,
NetworkUpgrade::Sapling, 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(); let hash = hasher.sighash();
@ -727,14 +743,14 @@ fn test_vec243_2() -> Result<()> {
let lock_script = Script::new(&[]); let lock_script = Script::new(&[]);
let prevout = transparent::Output { value, lock_script }; let prevout = transparent::Output { value, lock_script };
let index = input_ind as usize; 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( let alt_sighash = crate::primitives::zcash_primitives::sighash(
&transaction, &transaction,
HashType::NONE, HashType::NONE,
NetworkUpgrade::Sapling, 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); let result = hex::encode(alt_sighash);
assert_eq!(expected, result); assert_eq!(expected, result);
@ -753,12 +769,14 @@ fn test_vec243_3() -> Result<()> {
"76a914507173527b4c3318a2aecd793bf1cfed705950cf88ac", "76a914507173527b4c3318a2aecd793bf1cfed705950cf88ac",
)?); )?);
let input_ind = 0; let input_ind = 0;
let all_previous_outputs = vec![transparent::Output { value, lock_script }];
let hasher = SigHasher::new( let hasher = SigHasher::new(
&transaction, &transaction,
HashType::ALL, HashType::ALL,
NetworkUpgrade::Sapling, NetworkUpgrade::Sapling,
Some((input_ind, transparent::Output { value, lock_script })), &all_previous_outputs,
Some(input_ind),
); );
let hash = hasher.sighash(); let hash = hasher.sighash();
@ -778,14 +796,13 @@ fn test_vec243_3() -> Result<()> {
)?); )?);
let prevout = transparent::Output { value, lock_script }; let prevout = transparent::Output { value, lock_script };
let index = input_ind as usize; 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( let alt_sighash = crate::primitives::zcash_primitives::sighash(
&transaction, &transaction,
HashType::ALL, HashType::ALL,
NetworkUpgrade::Sapling, NetworkUpgrade::Sapling,
input, &[prevout],
Some(index),
); );
let result = hex::encode(alt_sighash); let result = hex::encode(alt_sighash);
assert_eq!(expected, result); assert_eq!(expected, result);
@ -799,22 +816,28 @@ fn zip143_sighash() -> Result<()> {
for (i, test) in zip0143::TEST_VECTORS.iter().enumerate() { for (i, test) in zip0143::TEST_VECTORS.iter().enumerate() {
let transaction = test.tx.zcash_deserialize_into::<Transaction>()?; let transaction = test.tx.zcash_deserialize_into::<Transaction>()?;
let input = match test.transparent_input { let (input_index, output) = match test.transparent_input {
Some(transparent_input) => Some(( Some(transparent_input) => (
transparent_input, Some(transparent_input as usize),
transparent::Output { Some(transparent::Output {
value: test.amount.try_into()?, value: test.amount.try_into()?,
lock_script: transparent::Script::new(test.script_code.as_ref()), 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( let result = hex::encode(
transaction.sighash( transaction.sighash(
NetworkUpgrade::from_branch_id(test.consensus_branch_id) NetworkUpgrade::from_branch_id(test.consensus_branch_id)
.expect("must be a valid branch ID"), .expect("must be a valid branch ID"),
HashType::from_bits(test.hash_type).expect("must be a valid HashType"), HashType::from_bits(test.hash_type).expect("must be a valid HashType"),
input, &all_previous_outputs,
input_index,
), ),
); );
let expected = hex::encode(test.sighash); let expected = hex::encode(test.sighash);
@ -830,22 +853,28 @@ fn zip243_sighash() -> Result<()> {
for (i, test) in zip0243::TEST_VECTORS.iter().enumerate() { for (i, test) in zip0243::TEST_VECTORS.iter().enumerate() {
let transaction = test.tx.zcash_deserialize_into::<Transaction>()?; let transaction = test.tx.zcash_deserialize_into::<Transaction>()?;
let input = match test.transparent_input { let (input_index, output) = match test.transparent_input {
Some(transparent_input) => Some(( Some(transparent_input) => (
transparent_input, Some(transparent_input as usize),
transparent::Output { Some(transparent::Output {
value: test.amount.try_into()?, value: test.amount.try_into()?,
lock_script: transparent::Script::new(test.script_code.as_ref()), 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( let result = hex::encode(
transaction.sighash( transaction.sighash(
NetworkUpgrade::from_branch_id(test.consensus_branch_id) NetworkUpgrade::from_branch_id(test.consensus_branch_id)
.expect("must be a valid branch ID"), .expect("must be a valid branch ID"),
HashType::from_bits(test.hash_type).expect("must be a valid HashType"), HashType::from_bits(test.hash_type).expect("must be a valid HashType"),
input, &all_previous_outputs,
input_index,
), ),
); );
let expected = hex::encode(test.sighash); let expected = hex::encode(test.sighash);
@ -861,22 +890,35 @@ fn zip244_sighash() -> Result<()> {
for (i, test) in zip0244::TEST_VECTORS.iter().enumerate() { for (i, test) in zip0244::TEST_VECTORS.iter().enumerate() {
let transaction = test.tx.zcash_deserialize_into::<Transaction>()?; let transaction = test.tx.zcash_deserialize_into::<Transaction>()?;
let input = match test.amount { let (input_index, output) = match test.amount {
Some(amount) => Some(( Some(amount) => (
test.transparent_input Some(
.expect("test vector must have transparent_input when it has amount"), test.transparent_input
transparent::Output { .expect("test vector must have transparent_input when it has amount")
as usize,
),
Some(transparent::Output {
value: amount.try_into()?, value: amount.try_into()?,
lock_script: transparent::Script::new( lock_script: transparent::Script::new(
test.script_code test.script_code
.as_ref() .as_ref()
.expect("test vector must have script_code when it has amount"), .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); let expected = hex::encode(test.sighash_all);
assert_eq!(expected, result, "test #{}: sighash does not match", i); 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 { 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( let bvk = redjubjub::VerificationKey::try_from(
sapling_shielded_data.binding_verification_key(), 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 { 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( let bvk = redjubjub::VerificationKey::try_from(
sapling_shielded_data.binding_verification_key(), sapling_shielded_data.binding_verification_key(),

View File

@ -125,8 +125,8 @@ async fn check_transcripts() -> Result<(), Report> {
let network = Network::Mainnet; let network = Network::Mainnet;
let state_service = zebra_state::init_test(network); let state_service = zebra_state::init_test(network);
let script = script::Verifier::new(state_service.clone()); let script = script::Verifier::new();
let transaction = transaction::Verifier::new(network, script); let transaction = transaction::Verifier::new(network, state_service.clone(), script);
let transaction = Buffer::new(BoxService::new(transaction), 1); let transaction = Buffer::new(BoxService::new(transaction), 1);
let block_verifier = Buffer::new( let block_verifier = Buffer::new(
BlockVerifier::new(network, state_service.clone(), transaction), 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 block = large_single_transaction_block();
let mut legacy_sigop_count = 0; let mut legacy_sigop_count = 0;
for transaction in block.transactions { 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(); let tx_sigop_count = cached_ffi_transaction.legacy_sigop_count();
assert_eq!(tx_sigop_count, Ok(0)); assert_eq!(tx_sigop_count, Ok(0));
legacy_sigop_count += tx_sigop_count.expect("unexpected invalid sigop count"); 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 block = large_multi_transaction_block();
let mut legacy_sigop_count = 0; let mut legacy_sigop_count = 0;
for transaction in block.transactions { 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(); let tx_sigop_count = cached_ffi_transaction.legacy_sigop_count();
assert_eq!(tx_sigop_count, Ok(1)); assert_eq!(tx_sigop_count, Ok(1));
legacy_sigop_count += tx_sigop_count.expect("unexpected invalid sigop count"); 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() .zcash_deserialize_into()
.expect("block test vector is valid"); .expect("block test vector is valid");
for transaction in block.transactions { 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 += cached_ffi_transaction
.legacy_sigop_count() .legacy_sigop_count()
.expect("unexpected invalid sigop count"); .expect("unexpected invalid sigop count");

View File

@ -229,8 +229,8 @@ where
// transaction verification // transaction verification
let script = script::Verifier::new(state_service.clone()); let script = script::Verifier::new();
let transaction = transaction::Verifier::new(network, script); let transaction = transaction::Verifier::new(network, state_service.clone(), script);
let transaction = Buffer::new(BoxService::new(transaction), VERIFIER_BUFFER_BOUND); let transaction = Buffer::new(BoxService::new(transaction), VERIFIER_BUFFER_BOUND);
// block verification // 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 tracing::Instrument;
use zebra_chain::{parameters::NetworkUpgrade, transparent}; use zebra_chain::{parameters::NetworkUpgrade, transparent};
@ -8,20 +7,6 @@ use zebra_script::CachedFfiTransaction;
use crate::BoxError; 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. /// Asynchronous script verification.
/// ///
/// The verifier asynchronously requests the UTXO a transaction attempts /// 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]. /// The asynchronous script verification design is documented in [RFC4].
/// ///
/// [RFC4]: https://zebra.zfnd.org/dev/rfcs/0004-asynchronous-script-verification.html /// [RFC4]: https://zebra.zfnd.org/dev/rfcs/0004-asynchronous-script-verification.html
#[derive(Debug, Clone)] #[derive(Debug, Clone, Default)]
pub struct Verifier<ZS> { pub struct Verifier {}
state: Timeout<ZS>,
}
impl<ZS> Verifier<ZS> { impl Verifier {
pub fn new(state: ZS) -> Self { pub fn new() -> Self {
Self { Self {}
state: Timeout::new(state, UTXO_LOOKUP_TIMEOUT),
}
} }
} }
@ -55,10 +36,6 @@ pub struct Request {
/// ///
/// Coinbase inputs are rejected by the script verifier, because they do not spend a UTXO. /// Coinbase inputs are rejected by the script verifier, because they do not spend a UTXO.
pub input_index: usize, 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. /// The network upgrade active in the context of this verification request.
/// ///
/// Because the consensus branch ID changes with each network upgrade, /// Because the consensus branch ID changes with each network upgrade,
@ -66,36 +43,17 @@ pub struct Request {
pub upgrade: NetworkUpgrade, pub upgrade: NetworkUpgrade,
} }
/// A script verification response. impl tower::Service<Request> for Verifier {
/// type 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;
type Error = BoxError; type Error = BoxError;
type Future = type Future =
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>; Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
fn poll_ready( fn poll_ready(
&mut self, &mut self,
cx: &mut std::task::Context<'_>, _cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> { ) -> std::task::Poll<Result<(), Self::Error>> {
self.state.poll_ready(cx) std::task::Poll::Ready(Ok(()))
} }
fn call(&mut self, req: Request) -> Self::Future { fn call(&mut self, req: Request) -> Self::Future {
@ -104,7 +62,6 @@ where
let Request { let Request {
cached_ffi_transaction, cached_ffi_transaction,
input_index, input_index,
known_utxos,
upgrade, upgrade,
} = req; } = req;
let input = &cached_ffi_transaction.inputs()[input_index]; let input = &cached_ffi_transaction.inputs()[input_index];
@ -118,29 +75,12 @@ where
// Avoid calling the state service if the utxo is already known // Avoid calling the state service if the utxo is already known
let span = tracing::trace_span!("script", ?outpoint); let span = tracing::trace_span!("script", ?outpoint);
let query =
span.in_scope(|| self.state.call(zebra_state::Request::AwaitUtxo(outpoint)));
async move { async move {
tracing::trace!("awaiting outpoint lookup"); cached_ffi_transaction.is_valid(branch_id, input_index)?;
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))?;
tracing::trace!("script verification succeeded"); tracing::trace!("script verification succeeded");
Ok(Response { Ok(())
spent_outpoint: outpoint,
spent_utxo: utxo,
})
} }
.instrument(span) .instrument(span)
.boxed() .boxed()

View File

@ -15,8 +15,7 @@ use futures::{
stream::{FuturesUnordered, StreamExt}, stream::{FuturesUnordered, StreamExt},
FutureExt, TryFutureExt, FutureExt, TryFutureExt,
}; };
use tokio::sync::mpsc; use tower::{timeout::Timeout, Service, ServiceExt};
use tower::{Service, ServiceExt};
use tracing::Instrument; use tracing::Instrument;
use zebra_chain::{ use zebra_chain::{
@ -28,7 +27,7 @@ use zebra_chain::{
transaction::{ transaction::{
self, HashType, SigHash, Transaction, UnminedTx, UnminedTxId, VerifiedUnminedTx, self, HashType, SigHash, Transaction, UnminedTx, UnminedTxId, VerifiedUnminedTx,
}, },
transparent, transparent::{self, OrderedUtxo},
}; };
use zebra_script::CachedFfiTransaction; use zebra_script::CachedFfiTransaction;
@ -40,6 +39,20 @@ pub mod check;
#[cfg(test)] #[cfg(test)]
mod tests; 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. /// Asynchronous transaction verification.
/// ///
/// # Correctness /// # Correctness
@ -50,7 +63,8 @@ mod tests;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Verifier<ZS> { pub struct Verifier<ZS> {
network: Network, network: Network,
script_verifier: script::Verifier<ZS>, state: Timeout<ZS>,
script_verifier: script::Verifier,
} }
impl<ZS> Verifier<ZS> impl<ZS> Verifier<ZS>
@ -59,9 +73,10 @@ where
ZS::Future: Send + 'static, ZS::Future: Send + 'static,
{ {
/// Create a new transaction verifier. /// 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 { Self {
network, network,
state: Timeout::new(state, UTXO_LOOKUP_TIMEOUT),
script_verifier, script_verifier,
} }
} }
@ -281,6 +296,7 @@ where
fn call(&mut self, req: Request) -> Self::Future { fn call(&mut self, req: Request) -> Self::Future {
let script_verifier = self.script_verifier.clone(); let script_verifier = self.script_verifier.clone();
let network = self.network; let network = self.network;
let state = self.state.clone();
let tx = req.transaction(); let tx = req.transaction();
let tx_id = req.tx_id(); let tx_id = req.tx_id();
@ -289,10 +305,6 @@ where
async move { async move {
tracing::trace!(?req); 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 // Do basic checks first
if let Some(block_time) = req.block_time() { if let Some(block_time) = req.block_time() {
check::lock_time_has_passed(&tx, req.height(), block_time)?; check::lock_time_has_passed(&tx, req.height(), block_time)?;
@ -338,7 +350,12 @@ where
// //
// https://zips.z.cash/zip-0213#specification // 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() { let async_checks = match tx.as_ref() {
Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => { Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => {
tracing::debug!(?tx, "got transaction with wrong version"); tracing::debug!(?tx, "got transaction with wrong version");
@ -353,7 +370,6 @@ where
network, network,
script_verifier, script_verifier,
cached_ffi_transaction.clone(), cached_ffi_transaction.clone(),
utxo_sender,
joinsplit_data, joinsplit_data,
sapling_shielded_data, sapling_shielded_data,
)?, )?,
@ -366,7 +382,6 @@ where
network, network,
script_verifier, script_verifier,
cached_ffi_transaction.clone(), cached_ffi_transaction.clone(),
utxo_sender,
sapling_shielded_data, sapling_shielded_data,
orchard_shielded_data, orchard_shielded_data,
)?, )?,
@ -376,11 +391,6 @@ where
// Zebra will timeout here, waiting for the async checks. // Zebra will timeout here, waiting for the async checks.
async_checks.check().await?; 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. // Get the `value_balance` to calculate the transaction fee.
let value_balance = tx.value_balance(&spent_utxos); 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: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
ZS::Future: Send + '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. /// Verify a V4 transaction.
/// ///
/// Returns a set of asynchronous checks that must all succeed for the transaction to be /// Returns a set of asynchronous checks that must all succeed for the transaction to be
@ -446,9 +508,8 @@ where
fn verify_v4_transaction( fn verify_v4_transaction(
request: &Request, request: &Request,
network: Network, network: Network,
script_verifier: script::Verifier<ZS>, script_verifier: script::Verifier,
cached_ffi_transaction: Arc<CachedFfiTransaction>, cached_ffi_transaction: Arc<CachedFfiTransaction>,
utxo_sender: mpsc::UnboundedSender<script::Response>,
joinsplit_data: &Option<transaction::JoinSplitData<Groth16Proof>>, joinsplit_data: &Option<transaction::JoinSplitData<Groth16Proof>>,
sapling_shielded_data: &Option<sapling::ShieldedData<sapling::PerSpendAnchor>>, sapling_shielded_data: &Option<sapling::ShieldedData<sapling::PerSpendAnchor>>,
) -> Result<AsyncChecks, TransactionError> { ) -> Result<AsyncChecks, TransactionError> {
@ -457,14 +518,18 @@ where
Self::verify_v4_transaction_network_upgrade(&tx, upgrade)?; 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( Ok(Self::verify_transparent_inputs_and_outputs(
request, request,
network, network,
script_verifier, script_verifier,
cached_ffi_transaction, cached_ffi_transaction,
utxo_sender,
)? )?
.and(Self::verify_sprout_shielded_data( .and(Self::verify_sprout_shielded_data(
joinsplit_data, joinsplit_data,
@ -528,9 +593,8 @@ where
fn verify_v5_transaction( fn verify_v5_transaction(
request: &Request, request: &Request,
network: Network, network: Network,
script_verifier: script::Verifier<ZS>, script_verifier: script::Verifier,
cached_ffi_transaction: Arc<CachedFfiTransaction>, cached_ffi_transaction: Arc<CachedFfiTransaction>,
utxo_sender: mpsc::UnboundedSender<script::Response>,
sapling_shielded_data: &Option<sapling::ShieldedData<sapling::SharedAnchor>>, sapling_shielded_data: &Option<sapling::ShieldedData<sapling::SharedAnchor>>,
orchard_shielded_data: &Option<orchard::ShieldedData>, orchard_shielded_data: &Option<orchard::ShieldedData>,
) -> Result<AsyncChecks, TransactionError> { ) -> Result<AsyncChecks, TransactionError> {
@ -539,14 +603,18 @@ where
Self::verify_v5_transaction_network_upgrade(&transaction, upgrade)?; 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( Ok(Self::verify_transparent_inputs_and_outputs(
request, request,
network, network,
script_verifier, script_verifier,
cached_ffi_transaction, cached_ffi_transaction,
utxo_sender,
)? )?
.and(Self::verify_sapling_shielded_data( .and(Self::verify_sapling_shielded_data(
sapling_shielded_data, sapling_shielded_data,
@ -597,9 +665,8 @@ where
fn verify_transparent_inputs_and_outputs( fn verify_transparent_inputs_and_outputs(
request: &Request, request: &Request,
network: Network, network: Network,
script_verifier: script::Verifier<ZS>, script_verifier: script::Verifier,
cached_ffi_transaction: Arc<CachedFfiTransaction>, cached_ffi_transaction: Arc<CachedFfiTransaction>,
utxo_sender: mpsc::UnboundedSender<script::Response>,
) -> Result<AsyncChecks, TransactionError> { ) -> Result<AsyncChecks, TransactionError> {
let transaction = request.transaction(); let transaction = request.transaction();
@ -611,24 +678,18 @@ where
// feed all of the inputs to the script verifier // feed all of the inputs to the script verifier
// the script_verifier also checks transparent sighashes, using its own implementation // the script_verifier also checks transparent sighashes, using its own implementation
let inputs = transaction.inputs(); let inputs = transaction.inputs();
let known_utxos = request.known_utxos();
let upgrade = request.upgrade(network); let upgrade = request.upgrade(network);
let script_checks = (0..inputs.len()) let script_checks = (0..inputs.len())
.into_iter() .into_iter()
.map(move |input_index| { .map(move |input_index| {
let utxo_sender = utxo_sender.clone();
let request = script::Request { let request = script::Request {
upgrade, upgrade,
known_utxos: known_utxos.clone(),
cached_ffi_transaction: cached_ffi_transaction.clone(), cached_ffi_transaction: cached_ffi_transaction.clone(),
input_index, input_index,
}; };
script_verifier.clone().oneshot(request).map_ok(move |rsp| { script_verifier.clone().oneshot(request).map_ok(|_r| {})
utxo_sender.send(rsp).expect("receiver is not dropped");
})
}) })
.collect(); .collect();

View File

@ -249,8 +249,8 @@ async fn v5_transaction_is_rejected_before_nu5_activation() {
for (network, blocks) in networks { for (network, blocks) in networks {
let state_service = service_fn(|_| async { unreachable!("Service should not be called") }); let state_service = service_fn(|_| async { unreachable!("Service should not be called") });
let script_verifier = script::Verifier::new(state_service); let script_verifier = script::Verifier::new();
let verifier = Verifier::new(network, script_verifier); let verifier = Verifier::new(network, state_service, script_verifier);
let transaction = fake_v5_transactions_for_network(network, blocks) let transaction = fake_v5_transactions_for_network(network, blocks)
.rev() .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 state_service = service_fn(|_| async { unreachable!("Service should not be called") });
let script_verifier = script::Verifier::new(state_service); let script_verifier = script::Verifier::new();
let verifier = Verifier::new(network, script_verifier); let verifier = Verifier::new(network, state_service, script_verifier);
let transaction = fake_v5_transactions_for_network(network, blocks) let transaction = fake_v5_transactions_for_network(network, blocks)
.rev() .rev()
@ -365,8 +365,8 @@ async fn v4_transaction_with_transparent_transfer_is_accepted() {
let state_service = let state_service =
service_fn(|_| async { unreachable!("State service should not be called") }); service_fn(|_| async { unreachable!("State service should not be called") });
let script_verifier = script::Verifier::new(state_service); let script_verifier = script::Verifier::new();
let verifier = Verifier::new(network, script_verifier); let verifier = Verifier::new(network, state_service, script_verifier);
let result = verifier let result = verifier
.oneshot(Request::Block { .oneshot(Request::Block {
@ -412,8 +412,8 @@ async fn v4_coinbase_transaction_is_accepted() {
let state_service = let state_service =
service_fn(|_| async { unreachable!("State service should not be called") }); service_fn(|_| async { unreachable!("State service should not be called") });
let script_verifier = script::Verifier::new(state_service); let script_verifier = script::Verifier::new();
let verifier = Verifier::new(network, script_verifier); let verifier = Verifier::new(network, state_service, script_verifier);
let result = verifier let result = verifier
.oneshot(Request::Block { .oneshot(Request::Block {
@ -463,8 +463,8 @@ async fn v4_transaction_with_transparent_transfer_is_rejected_by_the_script() {
let state_service = let state_service =
service_fn(|_| async { unreachable!("State service should not be called") }); service_fn(|_| async { unreachable!("State service should not be called") });
let script_verifier = script::Verifier::new(state_service); let script_verifier = script::Verifier::new();
let verifier = Verifier::new(network, script_verifier); let verifier = Verifier::new(network, state_service, script_verifier);
let result = verifier let result = verifier
.oneshot(Request::Block { .oneshot(Request::Block {
@ -514,8 +514,8 @@ async fn v4_transaction_with_conflicting_transparent_spend_is_rejected() {
let state_service = let state_service =
service_fn(|_| async { unreachable!("State service should not be called") }); service_fn(|_| async { unreachable!("State service should not be called") });
let script_verifier = script::Verifier::new(state_service); let script_verifier = script::Verifier::new();
let verifier = Verifier::new(network, script_verifier); let verifier = Verifier::new(network, state_service, script_verifier);
let result = verifier let result = verifier
.oneshot(Request::Block { .oneshot(Request::Block {
@ -569,7 +569,7 @@ fn v4_transaction_with_conflicting_sprout_nullifier_inside_joinsplit_is_rejected
}; };
// Sign the transaction // 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 { match &mut transaction {
Transaction::V4 { Transaction::V4 {
@ -581,8 +581,8 @@ fn v4_transaction_with_conflicting_sprout_nullifier_inside_joinsplit_is_rejected
let state_service = let state_service =
service_fn(|_| async { unreachable!("State service should not be called") }); service_fn(|_| async { unreachable!("State service should not be called") });
let script_verifier = script::Verifier::new(state_service); let script_verifier = script::Verifier::new();
let verifier = Verifier::new(network, script_verifier); let verifier = Verifier::new(network, state_service, script_verifier);
let result = verifier let result = verifier
.oneshot(Request::Block { .oneshot(Request::Block {
@ -641,7 +641,7 @@ fn v4_transaction_with_conflicting_sprout_nullifier_across_joinsplits_is_rejecte
}; };
// Sign the transaction // 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 { match &mut transaction {
Transaction::V4 { Transaction::V4 {
@ -653,8 +653,8 @@ fn v4_transaction_with_conflicting_sprout_nullifier_across_joinsplits_is_rejecte
let state_service = let state_service =
service_fn(|_| async { unreachable!("State service should not be called") }); service_fn(|_| async { unreachable!("State service should not be called") });
let script_verifier = script::Verifier::new(state_service); let script_verifier = script::Verifier::new();
let verifier = Verifier::new(network, script_verifier); let verifier = Verifier::new(network, state_service, script_verifier);
let result = verifier let result = verifier
.oneshot(Request::Block { .oneshot(Request::Block {
@ -708,8 +708,8 @@ async fn v5_transaction_with_transparent_transfer_is_accepted() {
let state_service = let state_service =
service_fn(|_| async { unreachable!("State service should not be called") }); service_fn(|_| async { unreachable!("State service should not be called") });
let script_verifier = script::Verifier::new(state_service); let script_verifier = script::Verifier::new();
let verifier = Verifier::new(network, script_verifier); let verifier = Verifier::new(network, state_service, script_verifier);
let result = verifier let result = verifier
.oneshot(Request::Block { .oneshot(Request::Block {
@ -758,8 +758,8 @@ async fn v5_coinbase_transaction_is_accepted() {
let state_service = let state_service =
service_fn(|_| async { unreachable!("State service should not be called") }); service_fn(|_| async { unreachable!("State service should not be called") });
let script_verifier = script::Verifier::new(state_service); let script_verifier = script::Verifier::new();
let verifier = Verifier::new(network, script_verifier); let verifier = Verifier::new(network, state_service, script_verifier);
let result = verifier let result = verifier
.oneshot(Request::Block { .oneshot(Request::Block {
@ -811,8 +811,8 @@ async fn v5_transaction_with_transparent_transfer_is_rejected_by_the_script() {
let state_service = let state_service =
service_fn(|_| async { unreachable!("State service should not be called") }); service_fn(|_| async { unreachable!("State service should not be called") });
let script_verifier = script::Verifier::new(state_service); let script_verifier = script::Verifier::new();
let verifier = Verifier::new(network, script_verifier); let verifier = Verifier::new(network, state_service, script_verifier);
let result = verifier let result = verifier
.oneshot(Request::Block { .oneshot(Request::Block {
@ -864,8 +864,8 @@ async fn v5_transaction_with_conflicting_transparent_spend_is_rejected() {
let state_service = let state_service =
service_fn(|_| async { unreachable!("State service should not be called") }); service_fn(|_| async { unreachable!("State service should not be called") });
let script_verifier = script::Verifier::new(state_service); let script_verifier = script::Verifier::new();
let verifier = Verifier::new(network, script_verifier); let verifier = Verifier::new(network, state_service, script_verifier);
let result = verifier let result = verifier
.oneshot(Request::Block { .oneshot(Request::Block {
@ -910,8 +910,8 @@ fn v4_with_signed_sprout_transfer_is_accepted() {
// Initialize the verifier // Initialize the verifier
let state_service = let state_service =
service_fn(|_| async { unreachable!("State service should not be called") }); service_fn(|_| async { unreachable!("State service should not be called") });
let script_verifier = script::Verifier::new(state_service); let script_verifier = script::Verifier::new();
let verifier = Verifier::new(network, script_verifier); let verifier = Verifier::new(network, state_service, script_verifier);
// Test the transaction verifier // Test the transaction verifier
let result = verifier let result = verifier
@ -984,8 +984,8 @@ async fn v4_with_joinsplit_is_rejected_for_modification(
// Initialize the verifier // Initialize the verifier
let state_service = let state_service =
service_fn(|_| async { unreachable!("State service should not be called") }); service_fn(|_| async { unreachable!("State service should not be called") });
let script_verifier = script::Verifier::new(state_service); let script_verifier = script::Verifier::new();
let verifier = Verifier::new(network, script_verifier); let verifier = Verifier::new(network, state_service, script_verifier);
// Test the transaction verifier // Test the transaction verifier
let result = verifier let result = verifier
@ -1022,8 +1022,8 @@ fn v4_with_sapling_spends() {
// Initialize the verifier // Initialize the verifier
let state_service = let state_service =
service_fn(|_| async { unreachable!("State service should not be called") }); service_fn(|_| async { unreachable!("State service should not be called") });
let script_verifier = script::Verifier::new(state_service); let script_verifier = script::Verifier::new();
let verifier = Verifier::new(network, script_verifier); let verifier = Verifier::new(network, state_service, script_verifier);
// Test the transaction verifier // Test the transaction verifier
let result = verifier let result = verifier
@ -1067,8 +1067,8 @@ fn v4_with_duplicate_sapling_spends() {
// Initialize the verifier // Initialize the verifier
let state_service = let state_service =
service_fn(|_| async { unreachable!("State service should not be called") }); service_fn(|_| async { unreachable!("State service should not be called") });
let script_verifier = script::Verifier::new(state_service); let script_verifier = script::Verifier::new();
let verifier = Verifier::new(network, script_verifier); let verifier = Verifier::new(network, state_service, script_verifier);
// Test the transaction verifier // Test the transaction verifier
let result = verifier let result = verifier
@ -1114,8 +1114,8 @@ fn v4_with_sapling_outputs_and_no_spends() {
// Initialize the verifier // Initialize the verifier
let state_service = let state_service =
service_fn(|_| async { unreachable!("State service should not be called") }); service_fn(|_| async { unreachable!("State service should not be called") });
let script_verifier = script::Verifier::new(state_service); let script_verifier = script::Verifier::new();
let verifier = Verifier::new(network, script_verifier); let verifier = Verifier::new(network, state_service, script_verifier);
// Test the transaction verifier // Test the transaction verifier
let result = verifier let result = verifier
@ -1162,8 +1162,8 @@ fn v5_with_sapling_spends() {
// Initialize the verifier // Initialize the verifier
let state_service = let state_service =
service_fn(|_| async { unreachable!("State service should not be called") }); service_fn(|_| async { unreachable!("State service should not be called") });
let script_verifier = script::Verifier::new(state_service); let script_verifier = script::Verifier::new();
let verifier = Verifier::new(network, script_verifier); let verifier = Verifier::new(network, state_service, script_verifier);
// Test the transaction verifier // Test the transaction verifier
let result = verifier let result = verifier
@ -1210,8 +1210,8 @@ fn v5_with_duplicate_sapling_spends() {
// Initialize the verifier // Initialize the verifier
let state_service = let state_service =
service_fn(|_| async { unreachable!("State service should not be called") }); service_fn(|_| async { unreachable!("State service should not be called") });
let script_verifier = script::Verifier::new(state_service); let script_verifier = script::Verifier::new();
let verifier = Verifier::new(network, script_verifier); let verifier = Verifier::new(network, state_service, script_verifier);
// Test the transaction verifier // Test the transaction verifier
let result = verifier let result = verifier
@ -1274,8 +1274,8 @@ fn v5_with_duplicate_orchard_action() {
// Initialize the verifier // Initialize the verifier
let state_service = let state_service =
service_fn(|_| async { unreachable!("State service should not be called") }); service_fn(|_| async { unreachable!("State service should not be called") });
let script_verifier = script::Verifier::new(state_service); let script_verifier = script::Verifier::new();
let verifier = Verifier::new(network, script_verifier); let verifier = Verifier::new(network, state_service, script_verifier);
// Test the transaction verifier // Test the transaction verifier
let result = verifier let result = verifier

View File

@ -441,8 +441,8 @@ fn validate(
// Initialize the verifier // Initialize the verifier
let state_service = let state_service =
tower::service_fn(|_| async { unreachable!("State service should not be called") }); tower::service_fn(|_| async { unreachable!("State service should not be called") });
let script_verifier = script::Verifier::new(state_service); let script_verifier = script::Verifier::new();
let verifier = transaction::Verifier::new(network, script_verifier); let verifier = transaction::Verifier::new(network, state_service, script_verifier);
// Test the transaction verifier // Test the transaction verifier
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. /// Transaction.
#[derive(Debug)] #[derive(Debug)]
pub struct CachedFfiTransaction { pub struct CachedFfiTransaction {
/// The deserialized Zebra transaction. /// 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>, 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. /// The deserialized `zcash_script` transaction, as a C++ object.
/// ///
/// SAFETY: this field must be private, /// SAFETY: this field must be private,
@ -74,8 +82,13 @@ pub struct CachedFfiTransaction {
} }
impl CachedFfiTransaction { impl CachedFfiTransaction {
/// Construct a `PrecomputedTransaction` from a `Transaction`. /// Construct a `PrecomputedTransaction` from a `Transaction` and the outputs
pub fn new(transaction: Arc<Transaction>) -> Self { /// 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 let tx_to = transaction
.zcash_serialize_to_vec() .zcash_serialize_to_vec()
.expect("serialization into a vec is infallible"); .expect("serialization into a vec is infallible");
@ -87,9 +100,26 @@ impl CachedFfiTransaction {
.expect("serialized transaction lengths are much less than u32::MAX"); .expect("serialized transaction lengths are much less than u32::MAX");
let mut err = 0; 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 { 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. // SAFETY: the safety of other methods depends on `precomputed` being valid and not NULL.
assert!( assert!(
@ -101,6 +131,7 @@ impl CachedFfiTransaction {
Self { Self {
transaction, transaction,
all_previous_outputs,
// SAFETY: `precomputed` must not be modified after initialisation, // SAFETY: `precomputed` must not be modified after initialisation,
// so that it is `Send` and `Sync`. // so that it is `Send` and `Sync`.
precomputed, precomputed,
@ -112,19 +143,21 @@ impl CachedFfiTransaction {
self.transaction.inputs() self.transaction.inputs()
} }
/// Verify a script within a transaction given the corresponding /// Returns the outputs from previous transactions that match each input in the transaction
/// `transparent::Output` it is spending and the `ConsensusBranchId` of the block /// being verified.
/// containing the transaction. pub fn all_previous_outputs(&self) -> &Vec<transparent::Output> {
/// &self.all_previous_outputs
/// # Details }
///
/// The `input_index` corresponds to the index of the `TransparentInput` which in /// Verify if the script in the input at `input_index` of a transaction correctly
/// `transaction` used to identify the `previous_output`. /// spends the matching `transparent::Output` it refers to, with the `ConsensusBranchId`
pub fn is_valid( /// of the block containing the transaction.
&self, pub fn is_valid(&self, branch_id: ConsensusBranchId, input_index: usize) -> Result<(), Error> {
branch_id: ConsensusBranchId, let previous_output = self
(input_index, previous_output): (u32, transparent::Output), .all_previous_outputs
) -> Result<(), Error> { .get(input_index)
.ok_or(Error::TxIndex)?
.clone();
let transparent::Output { value, lock_script } = previous_output; let transparent::Output { value, lock_script } = previous_output;
let script_pub_key: &[u8] = lock_script.as_raw_bytes(); 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 // The function `zcash_script:zcash_script_legacy_sigop_count_precomputed` only reads
// from the precomputed context. Currently, these reads happen after all the concurrent // from the precomputed context. Currently, these reads happen after all the concurrent
// async checks have finished. // 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 Send for CachedFfiTransaction {}
unsafe impl Sync for CachedFfiTransaction {} unsafe impl Sync for CachedFfiTransaction {}
@ -267,8 +306,9 @@ mod tests {
.branch_id() .branch_id()
.expect("Blossom has a ConsensusBranchId"); .expect("Blossom has a ConsensusBranchId");
let verifier = super::CachedFfiTransaction::new(transaction); let previous_output = vec![output];
verifier.is_valid(branch_id, (input_index, output))?; let verifier = super::CachedFfiTransaction::new(transaction, previous_output);
verifier.is_valid(branch_id, input_index)?;
Ok(()) Ok(())
} }
@ -280,7 +320,7 @@ mod tests {
let transaction = let transaction =
SCRIPT_TX.zcash_deserialize_into::<Arc<zebra_chain::transaction::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); assert_eq!(cached_tx.legacy_sigop_count()?, 1);
Ok(()) Ok(())
@ -303,10 +343,8 @@ mod tests {
.branch_id() .branch_id()
.expect("Blossom has a ConsensusBranchId"); .expect("Blossom has a ConsensusBranchId");
let verifier = super::CachedFfiTransaction::new(transaction); let verifier = super::CachedFfiTransaction::new(transaction, vec![output]);
verifier verifier.is_valid(branch_id, input_index).unwrap_err();
.is_valid(branch_id, (input_index, output))
.unwrap_err();
Ok(()) Ok(())
} }
@ -318,27 +356,22 @@ mod tests {
let coin = u64::pow(10, 8); let coin = u64::pow(10, 8);
let transaction = let transaction =
SCRIPT_TX.zcash_deserialize_into::<Arc<zebra_chain::transaction::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 input_index = 0;
let branch_id = Blossom let branch_id = Blossom
.branch_id() .branch_id()
.expect("Blossom has a ConsensusBranchId"); .expect("Blossom has a ConsensusBranchId");
let amount = 212 * coin; verifier.is_valid(branch_id, input_index)?;
let output = transparent::Output {
value: amount.try_into()?,
lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()),
};
verifier.is_valid(branch_id, (input_index, output))?;
let amount = 212 * coin; verifier.is_valid(branch_id, input_index)?;
let output = transparent::Output {
value: amount.try_into()?,
lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()),
};
verifier.is_valid(branch_id, (input_index, output))?;
Ok(()) Ok(())
} }
@ -348,31 +381,24 @@ mod tests {
zebra_test::init(); zebra_test::init();
let coin = u64::pow(10, 8); 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 = let transaction =
SCRIPT_TX.zcash_deserialize_into::<Arc<zebra_chain::transaction::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 input_index = 0;
let branch_id = Blossom let branch_id = Blossom
.branch_id() .branch_id()
.expect("Blossom has a ConsensusBranchId"); .expect("Blossom has a ConsensusBranchId");
let amount = 212 * coin; verifier.is_valid(branch_id, input_index)?;
let output = transparent::Output {
value: amount.try_into()?,
lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()),
};
verifier.is_valid(branch_id, (input_index, output))?;
let amount = 211 * coin; verifier.is_valid(branch_id, input_index + 1).unwrap_err();
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();
Ok(()) Ok(())
} }
@ -382,31 +408,24 @@ mod tests {
zebra_test::init(); zebra_test::init();
let coin = u64::pow(10, 8); 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 = let transaction =
SCRIPT_TX.zcash_deserialize_into::<Arc<zebra_chain::transaction::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 input_index = 0;
let branch_id = Blossom let branch_id = Blossom
.branch_id() .branch_id()
.expect("Blossom has a ConsensusBranchId"); .expect("Blossom has a ConsensusBranchId");
let amount = 211 * coin; verifier.is_valid(branch_id, input_index + 1).unwrap_err();
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();
let amount = 212 * coin; verifier.is_valid(branch_id, input_index)?;
let output = transparent::Output {
value: amount.try_into()?,
lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()),
};
verifier.is_valid(branch_id, (input_index, output))?;
Ok(()) Ok(())
} }
@ -416,33 +435,24 @@ mod tests {
zebra_test::init(); zebra_test::init();
let coin = u64::pow(10, 8); 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 = let transaction =
SCRIPT_TX.zcash_deserialize_into::<Arc<zebra_chain::transaction::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 input_index = 0;
let branch_id = Blossom let branch_id = Blossom
.branch_id() .branch_id()
.expect("Blossom has a ConsensusBranchId"); .expect("Blossom has a ConsensusBranchId");
let amount = 211 * coin; verifier.is_valid(branch_id, input_index + 1).unwrap_err();
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();
let amount = 210 * coin; verifier.is_valid(branch_id, input_index + 1).unwrap_err();
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();
Ok(()) Ok(())
} }