diff --git a/zebra-chain/src/serialization/error.rs b/zebra-chain/src/serialization/error.rs index e545901ceb7..dcbfb5c6ee9 100644 --- a/zebra-chain/src/serialization/error.rs +++ b/zebra-chain/src/serialization/error.rs @@ -1,4 +1,4 @@ -use std::{io, num::TryFromIntError}; +use std::{array::TryFromSliceError, io, num::TryFromIntError, str::Utf8Error}; use thiserror::Error; @@ -9,13 +9,26 @@ pub enum SerializationError { /// An io error that prevented deserialization #[error("io error: {0}")] Io(#[from] io::Error), + /// The data to be deserialized was malformed. // XXX refine errors #[error("parse error: {0}")] Parse(&'static str), + + /// A string was not UTF-8. + /// + /// Note: Rust `String` and `str` are always UTF-8. + #[error("string was not UTF-8: {0}")] + Utf8Error(#[from] Utf8Error), + + /// A slice was an unexpected length during deserialization. + #[error("slice was the wrong length: {0}")] + TryFromSliceError(#[from] TryFromSliceError), + /// The length of a vec is too large to convert to a usize (and thus, too large to allocate on this platform) #[error("compactsize too large: {0}")] TryFromIntError(#[from] TryFromIntError), + /// An error caused when validating a zatoshi `Amount` #[error("input couldn't be parsed as a zatoshi `Amount`: {source}")] Amount { @@ -23,6 +36,7 @@ pub enum SerializationError { #[from] source: crate::amount::Error, }, + /// Invalid transaction with a non-zero balance and no Sapling shielded spends or outputs. /// /// Transaction does not conform to the Sapling [consensus diff --git a/zebra-chain/src/transaction.rs b/zebra-chain/src/transaction.rs index 97d738fc052..1f268b7d297 100644 --- a/zebra-chain/src/transaction.rs +++ b/zebra-chain/src/transaction.rs @@ -18,7 +18,7 @@ pub mod arbitrary; mod tests; pub use auth_digest::AuthDigest; -pub use hash::Hash; +pub use hash::{Hash, WtxId}; pub use joinsplit::JoinSplitData; pub use lock_time::LockTime; pub use memo::Memo; diff --git a/zebra-chain/src/transaction/auth_digest.rs b/zebra-chain/src/transaction/auth_digest.rs index 878d7d6d32f..914e64e9b33 100644 --- a/zebra-chain/src/transaction/auth_digest.rs +++ b/zebra-chain/src/transaction/auth_digest.rs @@ -1,20 +1,108 @@ -use crate::primitives::zcash_primitives::auth_digest; +use std::fmt; + +#[cfg(any(test, feature = "proptest-impl"))] +use proptest_derive::Arbitrary; + +use crate::{ + primitives::zcash_primitives::auth_digest, + serialization::{ + ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashSerialize, + }, +}; use super::Transaction; /// An authorizing data commitment hash as specified in [ZIP-244]. /// -/// [ZIP-244]: https://zips.z.cash/zip-0244.. -#[derive(Copy, Clone, Eq, PartialEq, Debug)] +/// Note: Zebra displays transaction and block hashes in big-endian byte-order, +/// following the u256 convention set by Bitcoin and zcashd. +/// +/// [ZIP-244]: https://zips.z.cash/zip-0244 +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] pub struct AuthDigest(pub(crate) [u8; 32]); -impl<'a> From<&'a Transaction> for AuthDigest { +impl From for AuthDigest { + /// Computes the authorizing data commitment for a transaction. + /// + /// # Panics + /// + /// If passed a pre-v5 transaction. + fn from(transaction: Transaction) -> Self { + // use the ref implementation, to avoid cloning the transaction + AuthDigest::from(&transaction) + } +} + +impl From<&Transaction> for AuthDigest { /// Computes the authorizing data commitment for a transaction. /// /// # Panics /// /// If passed a pre-v5 transaction. - fn from(transaction: &'a Transaction) -> Self { + fn from(transaction: &Transaction) -> Self { auth_digest(transaction) } } + +impl From<[u8; 32]> for AuthDigest { + fn from(bytes: [u8; 32]) -> Self { + Self(bytes) + } +} + +impl From for [u8; 32] { + fn from(auth_digest: AuthDigest) -> Self { + auth_digest.0 + } +} + +impl From<&AuthDigest> for [u8; 32] { + fn from(auth_digest: &AuthDigest) -> Self { + (*auth_digest).into() + } +} + +impl fmt::Display for AuthDigest { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut reversed_bytes = self.0; + reversed_bytes.reverse(); + f.write_str(&hex::encode(&reversed_bytes)) + } +} + +impl fmt::Debug for AuthDigest { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut reversed_bytes = self.0; + reversed_bytes.reverse(); + f.debug_tuple("AuthDigest") + .field(&hex::encode(reversed_bytes)) + .finish() + } +} + +impl std::str::FromStr for AuthDigest { + type Err = SerializationError; + + fn from_str(s: &str) -> Result { + let mut bytes = [0; 32]; + if hex::decode_to_slice(s, &mut bytes[..]).is_err() { + Err(SerializationError::Parse("hex decoding error")) + } else { + bytes.reverse(); + Ok(AuthDigest(bytes)) + } + } +} + +impl ZcashSerialize for AuthDigest { + fn zcash_serialize(&self, mut writer: W) -> Result<(), std::io::Error> { + writer.write_32_bytes(&self.into()) + } +} + +impl ZcashDeserialize for AuthDigest { + fn zcash_deserialize(mut reader: R) -> Result { + Ok(reader.read_32_bytes()?.into()) + } +} diff --git a/zebra-chain/src/transaction/hash.rs b/zebra-chain/src/transaction/hash.rs index e8188055fb2..c1291adbb6a 100644 --- a/zebra-chain/src/transaction/hash.rs +++ b/zebra-chain/src/transaction/hash.rs @@ -1,15 +1,31 @@ -#![allow(clippy::unit_arg)] -use std::fmt; +//! Transaction identifiers for Zcash. +//! +//! Zcash has two different transaction identifiers, with different widths: +//! * [`Hash`]: a 32-byte narrow transaction ID, which uniquely identifies mined transactions +//! (transactions that have been committed to the blockchain in blocks), and +//! * [`WtxId`]: a 64-byte wide transaction ID, which uniquely identifies unmined transactions +//! (transactions that are sent by wallets or stored in node mempools). +//! +//! Transaction versions 1-4 are uniquely identified by narrow transaction IDs, +//! so Zebra and the Zcash network protocol don't use wide transaction IDs for them. + +use std::{ + convert::{TryFrom, TryInto}, + fmt, +}; #[cfg(any(test, feature = "proptest-impl"))] use proptest_derive::Arbitrary; use serde::{Deserialize, Serialize}; -use crate::serialization::SerializationError; +use crate::serialization::{ + ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashSerialize, +}; -use super::{txid::TxIdBuilder, Transaction}; +use super::{txid::TxIdBuilder, AuthDigest, Transaction}; -/// A transaction hash. +/// A narrow transaction ID, which uniquely identifies mined v5 transactions, +/// and all v1-v4 transactions. /// /// Note: Zebra displays transaction and block hashes in big-endian byte-order, /// following the u256 convention set by Bitcoin and zcashd. @@ -17,8 +33,15 @@ use super::{txid::TxIdBuilder, Transaction}; #[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] pub struct Hash(pub [u8; 32]); -impl<'a> From<&'a Transaction> for Hash { - fn from(transaction: &'a Transaction) -> Self { +impl From for Hash { + fn from(transaction: Transaction) -> Self { + // use the ref implementation, to avoid cloning the transaction + Hash::from(&transaction) + } +} + +impl From<&Transaction> for Hash { + fn from(transaction: &Transaction) -> Self { let hasher = TxIdBuilder::new(transaction); hasher .txid() @@ -26,6 +49,24 @@ impl<'a> From<&'a Transaction> for Hash { } } +impl From<[u8; 32]> for Hash { + fn from(bytes: [u8; 32]) -> Self { + Self(bytes) + } +} + +impl From for [u8; 32] { + fn from(hash: Hash) -> Self { + hash.0 + } +} + +impl From<&Hash> for [u8; 32] { + fn from(hash: &Hash) -> Self { + (*hash).into() + } +} + impl fmt::Display for Hash { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut reversed_bytes = self.0; @@ -58,20 +99,160 @@ impl std::str::FromStr for Hash { } } -#[cfg(test)] -mod tests { - use super::*; +impl ZcashSerialize for Hash { + fn zcash_serialize(&self, mut writer: W) -> Result<(), std::io::Error> { + writer.write_32_bytes(&self.into()) + } +} + +impl ZcashDeserialize for Hash { + fn zcash_deserialize(mut reader: R) -> Result { + Ok(reader.read_32_bytes()?.into()) + } +} + +/// A wide transaction ID, which uniquely identifies unmined v5 transactions. +/// +/// Wide transaction IDs are not used for transaction versions 1-4. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] +pub struct WtxId { + /// The non-malleable transaction ID for this transaction's effects. + pub id: Hash, + + /// The authorizing data digest for this transactions signatures, proofs, and scripts. + pub auth_digest: AuthDigest, +} + +impl WtxId { + /// Return this wide transaction ID as a serialized byte array. + pub fn as_bytes(&self) -> [u8; 64] { + <[u8; 64]>::from(self) + } +} + +impl From for WtxId { + /// Computes the wide transaction ID for a transaction. + /// + /// # Panics + /// + /// If passed a pre-v5 transaction. + fn from(transaction: Transaction) -> Self { + // use the ref implementation, to avoid cloning the transaction + WtxId::from(&transaction) + } +} + +impl From<&Transaction> for WtxId { + /// Computes the wide transaction ID for a transaction. + /// + /// # Panics + /// + /// If passed a pre-v5 transaction. + fn from(transaction: &Transaction) -> Self { + Self { + id: transaction.into(), + auth_digest: transaction.into(), + } + } +} + +impl From<[u8; 64]> for WtxId { + fn from(bytes: [u8; 64]) -> Self { + let id: [u8; 32] = bytes[0..32].try_into().expect("length is 64"); + let auth_digest: [u8; 32] = bytes[32..64].try_into().expect("length is 64"); + + Self { + id: id.into(), + auth_digest: auth_digest.into(), + } + } +} - #[test] - fn transactionhash_from_str() { - zebra_test::init(); +impl From for [u8; 64] { + fn from(wtx_id: WtxId) -> Self { + let mut bytes = [0; 64]; + let (id, auth_digest) = bytes.split_at_mut(32); + + id.copy_from_slice(&wtx_id.id.0); + auth_digest.copy_from_slice(&wtx_id.auth_digest.0); + + bytes + } +} + +impl From<&WtxId> for [u8; 64] { + fn from(wtx_id: &WtxId) -> Self { + (*wtx_id).into() + } +} + +impl TryFrom<&[u8]> for WtxId { + type Error = SerializationError; + + fn try_from(bytes: &[u8]) -> Result { + let bytes: [u8; 64] = bytes.try_into()?; + + Ok(bytes.into()) + } +} + +impl TryFrom> for WtxId { + type Error = SerializationError; + + fn try_from(bytes: Vec) -> Result { + bytes.as_slice().try_into() + } +} + +impl TryFrom<&Vec> for WtxId { + type Error = SerializationError; + + fn try_from(bytes: &Vec) -> Result { + bytes.clone().try_into() + } +} + +impl fmt::Display for WtxId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(&self.id.to_string())?; + f.write_str(&self.auth_digest.to_string()) + } +} + +impl std::str::FromStr for WtxId { + type Err = SerializationError; + + fn from_str(s: &str) -> Result { + // we need to split using bytes, + // because str::split_at panics if it splits a UTF-8 codepoint + let s = s.as_bytes(); + + if s.len() == 128 { + let (id, auth_digest) = s.split_at(64); + let id = std::str::from_utf8(id)?; + let auth_digest = std::str::from_utf8(auth_digest)?; + + Ok(Self { + id: id.parse()?, + auth_digest: auth_digest.parse()?, + }) + } else { + Err(SerializationError::Parse( + "wrong length for WtxId hex string", + )) + } + } +} + +impl ZcashSerialize for WtxId { + fn zcash_serialize(&self, mut writer: W) -> Result<(), std::io::Error> { + writer.write_64_bytes(&self.into()) + } +} - let hash: Hash = "3166411bd5343e0b284a108f39a929fbbb62619784f8c6dafe520703b5b446bf" - .parse() - .unwrap(); - assert_eq!( - format!("{:?}", hash), - r#"transaction::Hash("3166411bd5343e0b284a108f39a929fbbb62619784f8c6dafe520703b5b446bf")"# - ); +impl ZcashDeserialize for WtxId { + fn zcash_deserialize(mut reader: R) -> Result { + Ok(reader.read_64_bytes()?.into()) } } diff --git a/zebra-chain/src/transaction/tests/prop.rs b/zebra-chain/src/transaction/tests/prop.rs index f1cebf535f4..e0a89f6b771 100644 --- a/zebra-chain/src/transaction/tests/prop.rs +++ b/zebra-chain/src/transaction/tests/prop.rs @@ -33,7 +33,7 @@ proptest! { } #[test] - fn transaction_hash_display_fromstr_roundtrip(hash in any::()) { + fn transaction_hash_struct_display_roundtrip(hash in any::()) { zebra_test::init(); let display = format!("{}", hash); @@ -41,6 +41,54 @@ proptest! { prop_assert_eq!(hash, parsed); } + #[test] + fn transaction_hash_string_parse_roundtrip(hash in any::()) { + zebra_test::init(); + + if let Ok(parsed) = hash.parse::() { + let display = format!("{}", parsed); + prop_assert_eq!(hash, display); + } + } + + #[test] + fn transaction_auth_digest_struct_display_roundtrip(auth_digest in any::()) { + zebra_test::init(); + + let display = format!("{}", auth_digest); + let parsed = display.parse::().expect("auth digest should parse"); + prop_assert_eq!(auth_digest, parsed); + } + + #[test] + fn transaction_auth_digest_string_parse_roundtrip(auth_digest in any::()) { + zebra_test::init(); + + if let Ok(parsed) = auth_digest.parse::() { + let display = format!("{}", parsed); + prop_assert_eq!(auth_digest, display); + } + } + + #[test] + fn transaction_wtx_id_struct_display_roundtrip(wtx_id in any::()) { + zebra_test::init(); + + let display = format!("{}", wtx_id); + let parsed = display.parse::().expect("wide transaction ID should parse"); + prop_assert_eq!(wtx_id, parsed); + } + + #[test] + fn transaction_wtx_id_string_parse_roundtrip(wtx_id in any::()) { + zebra_test::init(); + + if let Ok(parsed) = wtx_id.parse::() { + let display = format!("{}", parsed); + prop_assert_eq!(wtx_id, display); + } + } + #[test] fn locktime_roundtrip(locktime in any::()) { zebra_test::init(); diff --git a/zebra-chain/src/transaction/tests/vectors.rs b/zebra-chain/src/transaction/tests/vectors.rs index 021d7976d34..24e4936273e 100644 --- a/zebra-chain/src/transaction/tests/vectors.rs +++ b/zebra-chain/src/transaction/tests/vectors.rs @@ -3,20 +3,21 @@ use std::convert::{TryFrom, TryInto}; use color_eyre::eyre::Result; use lazy_static::lazy_static; -use zebra_test::{zip0143, zip0243, zip0244}; - -use super::super::*; use crate::{ + amount::Amount, block::{Block, Height, MAX_BLOCK_BYTES}, parameters::{Network, NetworkUpgrade}, serialization::{SerializationError, ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize}, - transaction::{sighash::SigHasher, txid::TxIdBuilder}, + transaction::{hash::WtxId, sighash::SigHasher, txid::TxIdBuilder, Transaction}, + transparent::Script, }; -use crate::{amount::Amount, transaction::Transaction}; +use zebra_test::{ + vectors::{ZIP143_1, ZIP143_2, ZIP243_1, ZIP243_2, ZIP243_3}, + zip0143, zip0243, zip0244, +}; -use transparent::Script; -use zebra_test::vectors::{ZIP143_1, ZIP143_2, ZIP243_1, ZIP243_2, ZIP243_3}; +use super::super::*; lazy_static! { pub static ref EMPTY_V5_TX: Transaction = Transaction::V5 { @@ -30,6 +31,60 @@ lazy_static! { }; } +#[test] +fn transactionhash_struct_from_str_roundtrip() { + zebra_test::init(); + + let hash: Hash = "3166411bd5343e0b284a108f39a929fbbb62619784f8c6dafe520703b5b446bf" + .parse() + .unwrap(); + + assert_eq!( + format!("{:?}", hash), + r#"transaction::Hash("3166411bd5343e0b284a108f39a929fbbb62619784f8c6dafe520703b5b446bf")"# + ); + assert_eq!( + hash.to_string(), + "3166411bd5343e0b284a108f39a929fbbb62619784f8c6dafe520703b5b446bf" + ); +} + +#[test] +fn auth_digest_struct_from_str_roundtrip() { + zebra_test::init(); + + let digest: AuthDigest = "3166411bd5343e0b284a108f39a929fbbb62619784f8c6dafe520703b5b446bf" + .parse() + .unwrap(); + + assert_eq!( + format!("{:?}", digest), + r#"AuthDigest("3166411bd5343e0b284a108f39a929fbbb62619784f8c6dafe520703b5b446bf")"# + ); + assert_eq!( + digest.to_string(), + "3166411bd5343e0b284a108f39a929fbbb62619784f8c6dafe520703b5b446bf" + ); +} + +#[test] +fn wtx_id_struct_from_str_roundtrip() { + zebra_test::init(); + + let wtx_id: WtxId = "3166411bd5343e0b284a108f39a929fbbb62619784f8c6dafe520703b5b446bf0000000000000000000000000000000000000000000000000000000000000001" + .parse() + .unwrap(); + + assert_eq!( + format!("{:?}", wtx_id), + r#"WtxId { id: transaction::Hash("3166411bd5343e0b284a108f39a929fbbb62619784f8c6dafe520703b5b446bf"), auth_digest: AuthDigest("0000000000000000000000000000000000000000000000000000000000000001") }"# + ); + assert_eq!( + wtx_id.to_string(), + "3166411bd5343e0b284a108f39a929fbbb62619784f8c6dafe520703b5b446bf0000000000000000000000000000000000000000000000000000000000000001" + ); +} + #[test] fn librustzcash_tx_deserialize_and_round_trip() { zebra_test::init(); diff --git a/zebra-network/src/protocol/external/inv.rs b/zebra-network/src/protocol/external/inv.rs index 44b7df86916..d02cadd3628 100644 --- a/zebra-network/src/protocol/external/inv.rs +++ b/zebra-network/src/protocol/external/inv.rs @@ -7,7 +7,8 @@ use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use zebra_chain::{ block, serialization::{ - ReadZcashExt, SerializationError, TrustedPreallocate, ZcashDeserialize, ZcashSerialize, + ReadZcashExt, SerializationError, TrustedPreallocate, ZcashDeserialize, + ZcashDeserializeInto, ZcashSerialize, }, transaction, }; @@ -45,8 +46,21 @@ pub enum InventoryHash { /// [auth_digest]: https://zips.z.cash/zip-0244#authorizing-data-commitment /// [zip239]: https://zips.z.cash/zip-0239 /// [bip339]: https://github.com/bitcoin/bips/blob/master/bip-0339.mediawiki - // TODO: Actually handle this variant once the mempool is implemented - Wtx([u8; 64]), + // TODO: Actually handle this variant once the mempool is implemented (#2449) + Wtx(transaction::WtxId), +} + +impl InventoryHash { + /// Returns the serialized Zcash network protocol code for the current variant. + fn code(&self) -> u32 { + match self { + InventoryHash::Error => 0, + InventoryHash::Tx(_tx_id) => 1, + InventoryHash::Block(_hash) => 2, + InventoryHash::FilteredBlock(_hash) => 3, + InventoryHash::Wtx(_wtx_id) => 5, + } + } } impl From for InventoryHash { @@ -63,39 +77,43 @@ impl From for InventoryHash { } } +impl From for InventoryHash { + fn from(wtx_id: transaction::WtxId) -> InventoryHash { + InventoryHash::Wtx(wtx_id) + } +} + impl ZcashSerialize for InventoryHash { fn zcash_serialize(&self, mut writer: W) -> Result<(), std::io::Error> { - let (code, bytes): (_, &[u8]) = match self { - InventoryHash::Error => (0, &[0; 32]), - InventoryHash::Tx(hash) => (1, &hash.0), - InventoryHash::Block(hash) => (2, &hash.0), - InventoryHash::FilteredBlock(hash) => (3, &hash.0), - InventoryHash::Wtx(bytes) => (5, bytes), - }; - writer.write_u32::(code)?; - writer.write_all(bytes)?; - Ok(()) + writer.write_u32::(self.code())?; + match self { + // Zebra does not supply error codes + InventoryHash::Error => writer.write_all(&[0; 32]), + InventoryHash::Tx(tx_id) => tx_id.zcash_serialize(writer), + InventoryHash::Block(hash) => hash.zcash_serialize(writer), + InventoryHash::FilteredBlock(hash) => hash.zcash_serialize(writer), + InventoryHash::Wtx(wtx_id) => wtx_id.zcash_serialize(writer), + } } } impl ZcashDeserialize for InventoryHash { fn zcash_deserialize(mut reader: R) -> Result { let code = reader.read_u32::()?; - let bytes = reader.read_32_bytes()?; match code { - 0 => Ok(InventoryHash::Error), - 1 => Ok(InventoryHash::Tx(transaction::Hash(bytes))), - 2 => Ok(InventoryHash::Block(block::Hash(bytes))), - 3 => Ok(InventoryHash::FilteredBlock(block::Hash(bytes))), - 5 => { - let auth_digest = reader.read_32_bytes()?; + 0 => { + // ignore the standard 32-byte error code + let _bytes = reader.read_32_bytes()?; + Ok(InventoryHash::Error) + } - let mut wtx_bytes = [0u8; 64]; - wtx_bytes[..32].copy_from_slice(&bytes); - wtx_bytes[32..].copy_from_slice(&auth_digest); + 1 => Ok(InventoryHash::Tx(reader.zcash_deserialize_into()?)), + 2 => Ok(InventoryHash::Block(reader.zcash_deserialize_into()?)), + 3 => Ok(InventoryHash::FilteredBlock( + reader.zcash_deserialize_into()?, + )), + 5 => Ok(InventoryHash::Wtx(reader.zcash_deserialize_into()?)), - Ok(InventoryHash::Wtx(wtx_bytes)) - } _ => Err(SerializationError::Parse("invalid inventory code")), } } diff --git a/zebra-network/src/protocol/external/tests/prop.rs b/zebra-network/src/protocol/external/tests/prop.rs index 04c7a1f3aac..79e9d23a4f2 100644 --- a/zebra-network/src/protocol/external/tests/prop.rs +++ b/zebra-network/src/protocol/external/tests/prop.rs @@ -35,19 +35,19 @@ proptest! { fn inventory_hash_from_random_bytes(input in vec(any::(), 0..MAX_INVENTORY_HASH_BYTES)) { let deserialized: Result = input.zcash_deserialize_into(); - if input.len() < 36 { + if input.len() >= 4 && (input[1..4] != [0u8; 3] || input[0] > 5 || input[0] == 4) { + // Invalid inventory code + prop_assert!(matches!( + deserialized, + Err(SerializationError::Parse("invalid inventory code")) + )); + } else if input.len() < 36 { // Not enough bytes for any inventory hash prop_assert!(deserialized.is_err()); prop_assert_eq!( deserialized.unwrap_err().to_string(), "io error: failed to fill whole buffer" ); - } else if input[1..4] != [0u8; 3] || input[0] > 5 || input[0] == 4 { - // Invalid inventory code - prop_assert!(matches!( - deserialized, - Err(SerializationError::Parse("invalid inventory code")) - )); } else if input[0] == 5 && input.len() < 68 { // Not enough bytes for a WTX inventory hash prop_assert!(deserialized.is_err()); diff --git a/zebra-network/src/protocol/external/tests/vectors.rs b/zebra-network/src/protocol/external/tests/vectors.rs index e4342d27630..965ba02c518 100644 --- a/zebra-network/src/protocol/external/tests/vectors.rs +++ b/zebra-network/src/protocol/external/tests/vectors.rs @@ -22,5 +22,5 @@ fn parses_msg_wtx_inventory_type() { .zcash_deserialize_into() .expect("Failed to deserialize dummy `InventoryHash::Wtx`"); - assert_eq!(deserialized, InventoryHash::Wtx([0; 64])); + assert_eq!(deserialized, InventoryHash::Wtx([0u8; 64].into())); }