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:
parent
8939ddf3d8
commit
f270fd2de6
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue