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,
|
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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue