diff --git a/zebra-chain/src/block/tests/preallocate.rs b/zebra-chain/src/block/tests/preallocate.rs index 229b4048..902d251b 100644 --- a/zebra-chain/src/block/tests/preallocate.rs +++ b/zebra-chain/src/block/tests/preallocate.rs @@ -9,7 +9,10 @@ use crate::{ header::MIN_COUNTED_HEADER_LEN, CountedHeader, Hash, Header, BLOCK_HASH_SIZE, MAX_PROTOCOL_MESSAGE_LEN, }, - serialization::{CompactSizeMessage, TrustedPreallocate, ZcashSerialize}, + serialization::{ + arbitrary::max_allocation_is_big_enough, CompactSizeMessage, TrustedPreallocate, + ZcashSerialize, + }, }; proptest! { @@ -25,28 +28,22 @@ proptest! { /// 2. The largest allowed vector is small enough to fit in a legal Zcash Wire Protocol message #[test] fn block_hash_max_allocation(hash in Hash::arbitrary_with(())) { - let max_allocation: usize = Hash::max_allocation().try_into().unwrap(); - let mut smallest_disallowed_vec = Vec::with_capacity(max_allocation + 1); - for _ in 0..(Hash::max_allocation()+1) { - smallest_disallowed_vec.push(hash); - } + let ( + smallest_disallowed_vec_len, + smallest_disallowed_serialized_len, + largest_allowed_vec_len, + largest_allowed_serialized_len, + ) = max_allocation_is_big_enough(hash); - let smallest_disallowed_serialized = smallest_disallowed_vec.zcash_serialize_to_vec().expect("Serialization to vec must succeed"); // Check that our smallest_disallowed_vec is only one item larger than the limit - prop_assert!(((smallest_disallowed_vec.len() - 1) as u64) == Hash::max_allocation()); + prop_assert!(((smallest_disallowed_vec_len - 1) as u64) == Hash::max_allocation()); // Check that our smallest_disallowed_vec is too big to send as a protocol message - prop_assert!(smallest_disallowed_serialized.len() > MAX_PROTOCOL_MESSAGE_LEN); - - // Create largest_allowed_vec by removing one element from smallest_disallowed_vec without copying (for efficiency) - smallest_disallowed_vec.pop(); - let largest_allowed_vec = smallest_disallowed_vec; - let largest_allowed_serialized = largest_allowed_vec.zcash_serialize_to_vec().expect("Serialization to vec must succeed"); + prop_assert!(smallest_disallowed_serialized_len > MAX_PROTOCOL_MESSAGE_LEN); // Check that our largest_allowed_vec contains the maximum number of hashes - prop_assert!((largest_allowed_vec.len() as u64) == Hash::max_allocation()); + prop_assert!((largest_allowed_vec_len as u64) == Hash::max_allocation()); // Check that our largest_allowed_vec is small enough to send as a protocol message - prop_assert!(largest_allowed_serialized.len() <= MAX_PROTOCOL_MESSAGE_LEN); - + prop_assert!(largest_allowed_serialized_len <= MAX_PROTOCOL_MESSAGE_LEN); } /// Confirm that each counted header takes at least COUNTED_HEADER_LEN bytes when serialized. @@ -72,28 +69,24 @@ proptest! { fn counted_header_max_allocation(header in any::
()) { let header = CountedHeader { header, - transaction_count: 0.try_into().expect("zero fits in MAX_PROTOCOL_MESSAGE_LEN"), + transaction_count: 0.try_into().expect("zero is less than MAX_PROTOCOL_MESSAGE_LEN"), }; - let max_allocation: usize = CountedHeader::max_allocation().try_into().unwrap(); - let mut smallest_disallowed_vec = Vec::with_capacity(max_allocation + 1); - for _ in 0..(CountedHeader::max_allocation()+1) { - smallest_disallowed_vec.push(header); - } - let smallest_disallowed_serialized = smallest_disallowed_vec.zcash_serialize_to_vec().expect("Serialization to vec must succeed"); + + let ( + smallest_disallowed_vec_len, + smallest_disallowed_serialized_len, + largest_allowed_vec_len, + largest_allowed_serialized_len, + ) = max_allocation_is_big_enough(header); + // Check that our smallest_disallowed_vec is only one item larger than the limit - prop_assert!(((smallest_disallowed_vec.len() - 1) as u64) == CountedHeader::max_allocation()); + prop_assert!(((smallest_disallowed_vec_len - 1) as u64) == CountedHeader::max_allocation()); // Check that our smallest_disallowed_vec is too big to send as a protocol message - prop_assert!(smallest_disallowed_serialized.len() > MAX_PROTOCOL_MESSAGE_LEN); - - - // Create largest_allowed_vec by removing one element from smallest_disallowed_vec without copying (for efficiency) - smallest_disallowed_vec.pop(); - let largest_allowed_vec = smallest_disallowed_vec; - let largest_allowed_serialized = largest_allowed_vec.zcash_serialize_to_vec().expect("Serialization to vec must succeed"); + prop_assert!(smallest_disallowed_serialized_len > MAX_PROTOCOL_MESSAGE_LEN); // Check that our largest_allowed_vec contains the maximum number of CountedHeaders - prop_assert!((largest_allowed_vec.len() as u64) == CountedHeader::max_allocation()); + prop_assert!((largest_allowed_vec_len as u64) == CountedHeader::max_allocation()); // Check that our largest_allowed_vec is small enough to send as a protocol message - prop_assert!(largest_allowed_serialized.len() <= MAX_PROTOCOL_MESSAGE_LEN); + prop_assert!(largest_allowed_serialized_len <= MAX_PROTOCOL_MESSAGE_LEN); } } diff --git a/zebra-chain/src/orchard/tests/preallocate.rs b/zebra-chain/src/orchard/tests/preallocate.rs index 9fd87de2..d055e5fd 100644 --- a/zebra-chain/src/orchard/tests/preallocate.rs +++ b/zebra-chain/src/orchard/tests/preallocate.rs @@ -7,7 +7,7 @@ use crate::{ Action, AuthorizedAction, }, primitives::redpallas::{Signature, SpendAuth}, - serialization::{tests::max_allocation_is_big_enough, TrustedPreallocate, ZcashSerialize}, + serialization::{arbitrary::max_allocation_is_big_enough, TrustedPreallocate, ZcashSerialize}, }; use proptest::{prelude::*, proptest}; diff --git a/zebra-chain/src/sapling/tests/preallocate.rs b/zebra-chain/src/sapling/tests/preallocate.rs index f6d7c96e..518a0c65 100644 --- a/zebra-chain/src/sapling/tests/preallocate.rs +++ b/zebra-chain/src/sapling/tests/preallocate.rs @@ -14,7 +14,7 @@ use crate::{ }, PerSpendAnchor, SharedAnchor, }, - serialization::{tests::max_allocation_is_big_enough, TrustedPreallocate, ZcashSerialize}, + serialization::{arbitrary::max_allocation_is_big_enough, TrustedPreallocate, ZcashSerialize}, }; use proptest::prelude::*; diff --git a/zebra-chain/src/serialization/arbitrary.rs b/zebra-chain/src/serialization/arbitrary.rs index 8921af3d..2c84ad90 100644 --- a/zebra-chain/src/serialization/arbitrary.rs +++ b/zebra-chain/src/serialization/arbitrary.rs @@ -5,7 +5,9 @@ use std::convert::TryInto; use chrono::{TimeZone, Utc, MAX_DATETIME, MIN_DATETIME}; use proptest::{arbitrary::any, prelude::*}; -use super::{CompactSizeMessage, DateTime32, MAX_PROTOCOL_MESSAGE_LEN}; +use super::{ + CompactSizeMessage, DateTime32, TrustedPreallocate, ZcashSerialize, MAX_PROTOCOL_MESSAGE_LEN, +}; impl Arbitrary for DateTime32 { type Parameters = (); @@ -72,3 +74,40 @@ impl Arbitrary for CompactSizeMessage { type Strategy = BoxedStrategy; } + +/// Allocate a maximum-sized vector of cloned `item`s, and serialize that array. +/// +/// Returns the following calculations on `item`: +/// smallest_disallowed_vec_len +/// smallest_disallowed_serialized_len +/// largest_allowed_vec_len +/// largest_allowed_serialized_len +/// +/// For varible-size types, `largest_allowed_serialized_len` might not fit within +/// `MAX_PROTOCOL_MESSAGE_LEN` or `MAX_BLOCK_SIZE`. +pub fn max_allocation_is_big_enough(item: T) -> (usize, usize, usize, usize) +where + T: TrustedPreallocate + ZcashSerialize + Clone, +{ + let max_allocation: usize = T::max_allocation().try_into().unwrap(); + let mut smallest_disallowed_vec = vec![item; max_allocation + 1]; + + let smallest_disallowed_serialized = smallest_disallowed_vec + .zcash_serialize_to_vec() + .expect("Serialization to vec must succeed"); + let smallest_disallowed_vec_len = smallest_disallowed_vec.len(); + + // Create largest_allowed_vec by removing one element from smallest_disallowed_vec without copying (for efficiency) + smallest_disallowed_vec.pop(); + let largest_allowed_vec = smallest_disallowed_vec; + let largest_allowed_serialized = largest_allowed_vec + .zcash_serialize_to_vec() + .expect("Serialization to vec must succeed"); + + ( + smallest_disallowed_vec_len, + smallest_disallowed_serialized.len(), + largest_allowed_vec.len(), + largest_allowed_serialized.len(), + ) +} diff --git a/zebra-chain/src/serialization/tests.rs b/zebra-chain/src/serialization/tests.rs index d0b94745..a7ac2ac3 100644 --- a/zebra-chain/src/serialization/tests.rs +++ b/zebra-chain/src/serialization/tests.rs @@ -1,4 +1,2 @@ mod preallocate; mod prop; - -pub use preallocate::max_allocation_is_big_enough; diff --git a/zebra-chain/src/serialization/tests/preallocate.rs b/zebra-chain/src/serialization/tests/preallocate.rs index 2f7e6b3c..592e4b81 100644 --- a/zebra-chain/src/serialization/tests/preallocate.rs +++ b/zebra-chain/src/serialization/tests/preallocate.rs @@ -5,52 +5,31 @@ use proptest::{collection::size_range, prelude::*}; use std::{convert::TryInto, matches}; use crate::serialization::{ - zcash_deserialize::MAX_U8_ALLOCATION, SerializationError, TrustedPreallocate, ZcashDeserialize, - ZcashSerialize, MAX_PROTOCOL_MESSAGE_LEN, + arbitrary::max_allocation_is_big_enough, zcash_deserialize::MAX_U8_ALLOCATION, + SerializationError, TrustedPreallocate, ZcashDeserialize, ZcashSerialize, + MAX_PROTOCOL_MESSAGE_LEN, }; // Allow direct serialization of Vec for these tests. We don't usually // allow this because some types have specific rules for about serialization // of their inner Vec. This method could be easily misused if it applied // more generally. +// +// Due to Rust's trait rules, these trait impls apply to all zebra-chain tests, +// not just the tests in this module. But other crates' tests can't access them. impl ZcashSerialize for u8 { fn zcash_serialize(&self, mut writer: W) -> Result<(), std::io::Error> { writer.write_all(&[*self]) } } -/// Return the following calculations on `item`: -/// smallest_disallowed_vec_len -/// smallest_disallowed_serialized_len -/// largest_allowed_vec_len -/// largest_allowed_serialized_len -pub fn max_allocation_is_big_enough(item: T) -> (usize, usize, usize, usize) -where - T: TrustedPreallocate + ZcashSerialize + Clone, -{ - let max_allocation: usize = T::max_allocation().try_into().unwrap(); - let mut smallest_disallowed_vec = Vec::with_capacity(max_allocation + 1); - for _ in 0..(max_allocation + 1) { - smallest_disallowed_vec.push(item.clone()); +impl TrustedPreallocate for u8 { + fn max_allocation() -> u64 { + // MAX_PROTOCOL_MESSAGE_LEN takes up 5 bytes when encoded as a CompactSize. + (MAX_PROTOCOL_MESSAGE_LEN - 5) + .try_into() + .expect("MAX_PROTOCOL_MESSAGE_LEN fits in u64") } - let smallest_disallowed_serialized = smallest_disallowed_vec - .zcash_serialize_to_vec() - .expect("Serialization to vec must succeed"); - let smallest_disallowed_vec_len = smallest_disallowed_vec.len(); - - // Create largest_allowed_vec by removing one element from smallest_disallowed_vec without copying (for efficiency) - smallest_disallowed_vec.pop(); - let largest_allowed_vec = smallest_disallowed_vec; - let largest_allowed_serialized = largest_allowed_vec - .zcash_serialize_to_vec() - .expect("Serialization to vec must succeed"); - - ( - smallest_disallowed_vec_len, - smallest_disallowed_serialized.len(), - largest_allowed_vec.len(), - largest_allowed_serialized.len(), - ) } proptest! { @@ -111,25 +90,20 @@ fn u8_size_is_correct() { /// 1. The smallest disallowed `Vec` is too big to include in a Zcash Wire Protocol message /// 2. The largest allowed `Vec`is exactly the size of a maximal Zcash Wire Protocol message fn u8_max_allocation_is_correct() { - let mut shortest_disallowed_vec = vec![0u8; MAX_U8_ALLOCATION + 1]; - let shortest_disallowed_serialized = shortest_disallowed_vec - .zcash_serialize_to_vec() - .expect("Serialization to vec must succeed"); + let ( + smallest_disallowed_vec_len, + smallest_disallowed_serialized_len, + largest_allowed_vec_len, + largest_allowed_serialized_len, + ) = max_allocation_is_big_enough(0u8); // Confirm that shortest_disallowed_vec is only one item larger than the limit - assert_eq!((shortest_disallowed_vec.len() - 1), MAX_U8_ALLOCATION); + assert_eq!((smallest_disallowed_vec_len - 1), MAX_U8_ALLOCATION); // Confirm that shortest_disallowed_vec is too large to be included in a valid zcash message - assert!(shortest_disallowed_serialized.len() > MAX_PROTOCOL_MESSAGE_LEN); - - // Create largest_allowed_vec by removing one element from smallest_disallowed_vec without copying (for efficiency) - shortest_disallowed_vec.pop(); - let longest_allowed_vec = shortest_disallowed_vec; - let longest_allowed_serialized = longest_allowed_vec - .zcash_serialize_to_vec() - .expect("serialization to vec must succed"); + assert!(smallest_disallowed_serialized_len > MAX_PROTOCOL_MESSAGE_LEN); // Check that our largest_allowed_vec contains the maximum number of items - assert_eq!(longest_allowed_vec.len(), MAX_U8_ALLOCATION); + assert_eq!(largest_allowed_vec_len, MAX_U8_ALLOCATION); // Check that our largest_allowed_vec is the size of a maximal protocol message - assert_eq!(longest_allowed_serialized.len(), MAX_PROTOCOL_MESSAGE_LEN); + assert_eq!(largest_allowed_serialized_len, MAX_PROTOCOL_MESSAGE_LEN); } diff --git a/zebra-chain/src/serialization/zcash_deserialize.rs b/zebra-chain/src/serialization/zcash_deserialize.rs index a4340b0f..833e7bcd 100644 --- a/zebra-chain/src/serialization/zcash_deserialize.rs +++ b/zebra-chain/src/serialization/zcash_deserialize.rs @@ -2,6 +2,7 @@ use std::{ convert::{TryFrom, TryInto}, io, net::Ipv6Addr, + sync::Arc, }; use super::{AtLeastOne, CompactSizeMessage, SerializationError, MAX_PROTOCOL_MESSAGE_LEN}; @@ -171,6 +172,15 @@ pub trait TrustedPreallocate { fn max_allocation() -> u64; } +impl TrustedPreallocate for Arc +where + T: TrustedPreallocate, +{ + fn max_allocation() -> u64 { + T::max_allocation() + } +} + /// The length of the longest valid `Vec` that can be received over the network /// /// It takes 5 bytes to encode a CompactSize representing any number netween 2^16 and (2^32 - 1) diff --git a/zebra-chain/src/sprout/tests/preallocate.rs b/zebra-chain/src/sprout/tests/preallocate.rs index 46868329..b61c93fd 100644 --- a/zebra-chain/src/sprout/tests/preallocate.rs +++ b/zebra-chain/src/sprout/tests/preallocate.rs @@ -1,16 +1,14 @@ //! Tests for trusted preallocation during deserialization. -use super::super::joinsplit::{JoinSplit, BCTV14_JOINSPLIT_SIZE, GROTH16_JOINSPLIT_SIZE}; +use proptest::{prelude::*, proptest}; use crate::{ block::MAX_BLOCK_BYTES, primitives::{Bctv14Proof, Groth16Proof}, - serialization::{TrustedPreallocate, ZcashSerialize}, + serialization::{arbitrary::max_allocation_is_big_enough, TrustedPreallocate, ZcashSerialize}, + sprout::joinsplit::{JoinSplit, BCTV14_JOINSPLIT_SIZE, GROTH16_JOINSPLIT_SIZE}, }; -use proptest::{prelude::*, proptest}; -use std::convert::TryInto; - proptest! { /// Confirm that each JoinSplit takes exactly BCTV14_JOINSPLIT_SIZE bytes when serialized. /// This verifies that our calculated [`TrustedPreallocate::max_allocation`] is indeed an upper bound. @@ -37,29 +35,22 @@ proptest! { /// 2. The largest allowed vector is small enough to fit in a legal Zcash block #[test] fn joinsplit_btcv14_max_allocation_is_correct(joinsplit in >::arbitrary_with(())) { + let ( + smallest_disallowed_vec_len, + smallest_disallowed_serialized_len, + largest_allowed_vec_len, + largest_allowed_serialized_len, + ) = max_allocation_is_big_enough(joinsplit); - let max_allocation: usize = >::max_allocation().try_into().unwrap(); - let mut smallest_disallowed_vec = Vec::with_capacity(max_allocation + 1); - for _ in 0..(>::max_allocation()+1) { - smallest_disallowed_vec.push(joinsplit.clone()); - } - let smallest_disallowed_serialized = smallest_disallowed_vec.zcash_serialize_to_vec().expect("Serialization to vec must succeed"); // Check that our smallest_disallowed_vec is only one item larger than the limit - prop_assert!(((smallest_disallowed_vec.len() - 1) as u64) == >::max_allocation()); - // Check that our smallest_disallowed_vec is too big to be included in a valid block - // Note that a serialized block always includes at least one byte for the number of transactions, - // so any serialized Vec> at least MAX_BLOCK_BYTES long is too large to fit in a block. - prop_assert!((smallest_disallowed_serialized.len() as u64) >= MAX_BLOCK_BYTES); + prop_assert!(((smallest_disallowed_vec_len - 1) as u64) == >::max_allocation()); + // Check that our smallest_disallowed_vec is too big to fit in a valid Zcash Block. + prop_assert!(smallest_disallowed_serialized_len as u64 > MAX_BLOCK_BYTES); - // Create largest_allowed_vec by removing one element from smallest_disallowed_vec without copying (for efficiency) - smallest_disallowed_vec.pop(); - let largest_allowed_vec = smallest_disallowed_vec; - let largest_allowed_serialized = largest_allowed_vec.zcash_serialize_to_vec().expect("Serialization to vec must succeed"); - - // Check that our largest_allowed_vec contains the maximum number of > - prop_assert!((largest_allowed_vec.len() as u64) == >::max_allocation()); - // Check that our largest_allowed_vec is small enough to fit in a Zcash block. - prop_assert!((largest_allowed_serialized.len() as u64) < MAX_BLOCK_BYTES); + // Check that our largest_allowed_vec contains the maximum number of JoinSplits + prop_assert!((largest_allowed_vec_len as u64) == >::max_allocation()); + // Check that our largest_allowed_vec is small enough to fit in a Zcash Block. + prop_assert!(largest_allowed_serialized_len as u64 <= MAX_BLOCK_BYTES); } /// Verify that... @@ -68,27 +59,21 @@ proptest! { #[test] fn joinsplit_groth16_max_allocation_is_correct(joinsplit in >::arbitrary_with(())) { - let max_allocation: usize = >::max_allocation().try_into().unwrap(); - let mut smallest_disallowed_vec = Vec::with_capacity(max_allocation + 1); - for _ in 0..(>::max_allocation()+1) { - smallest_disallowed_vec.push(joinsplit.clone()); - } - let smallest_disallowed_serialized = smallest_disallowed_vec.zcash_serialize_to_vec().expect("Serialization to vec must succeed"); + let ( + smallest_disallowed_vec_len, + smallest_disallowed_serialized_len, + largest_allowed_vec_len, + largest_allowed_serialized_len, + ) = max_allocation_is_big_enough(joinsplit); + // Check that our smallest_disallowed_vec is only one item larger than the limit - prop_assert!(((smallest_disallowed_vec.len() - 1) as u64) == >::max_allocation()); - // Check that our smallest_disallowed_vec is too big to be included in a valid block - // Note that a serialized block always includes at least one byte for the number of transactions, - // so any serialized Vec> at least MAX_BLOCK_BYTES long is too large to fit in a block. - prop_assert!((smallest_disallowed_serialized.len() as u64) >= MAX_BLOCK_BYTES); + prop_assert!(((smallest_disallowed_vec_len - 1) as u64) == >::max_allocation()); + // Check that our smallest_disallowed_vec is too big to fit in a valid Zcash Block. + prop_assert!(smallest_disallowed_serialized_len as u64 > MAX_BLOCK_BYTES); - // Create largest_allowed_vec by removing one element from smallest_disallowed_vec without copying (for efficiency) - smallest_disallowed_vec.pop(); - let largest_allowed_vec = smallest_disallowed_vec; - let largest_allowed_serialized = largest_allowed_vec.zcash_serialize_to_vec().expect("Serialization to vec must succeed"); - - // Check that our largest_allowed_vec contains the maximum number of > - prop_assert!((largest_allowed_vec.len() as u64) == >::max_allocation()); - // Check that our largest_allowed_vec is small enough to fit in a Zcash block. - prop_assert!((largest_allowed_serialized.len() as u64) < MAX_BLOCK_BYTES); + // Check that our largest_allowed_vec contains the maximum number of JoinSplits + prop_assert!((largest_allowed_vec_len as u64) == >::max_allocation()); + // Check that our largest_allowed_vec is small enough to fit in a Zcash Block. + prop_assert!(largest_allowed_serialized_len as u64 <= MAX_BLOCK_BYTES); } } diff --git a/zebra-chain/src/transaction/serialize.rs b/zebra-chain/src/transaction/serialize.rs index cf342fd5..014cc7fa 100644 --- a/zebra-chain/src/transaction/serialize.rs +++ b/zebra-chain/src/transaction/serialize.rs @@ -717,11 +717,16 @@ where } /// A Tx Input must have an Outpoint (32 byte hash + 4 byte index), a 4 byte sequence number, -/// and a signature script, which always takes a min of 1 byte (for a length 0 script) +/// and a signature script, which always takes a min of 1 byte (for a length 0 script). pub(crate) const MIN_TRANSPARENT_INPUT_SIZE: u64 = 32 + 4 + 4 + 1; -/// A Transparent output has an 8 byte value and script which takes a min of 1 byte + +/// A Transparent output has an 8 byte value and script which takes a min of 1 byte. pub(crate) const MIN_TRANSPARENT_OUTPUT_SIZE: u64 = 8 + 1; -/// All txs must have at least one input, a 4 byte locktime, and at least one output + +/// All txs must have at least one input, a 4 byte locktime, and at least one output. +/// +/// Shielded transfers are much larger than transparent transfers, +/// so this is the minimum transaction size. pub(crate) const MIN_TRANSPARENT_TX_SIZE: u64 = MIN_TRANSPARENT_INPUT_SIZE + 4 + MIN_TRANSPARENT_OUTPUT_SIZE; @@ -729,7 +734,7 @@ pub(crate) const MIN_TRANSPARENT_TX_SIZE: u64 = /// /// `tx` messages contain a single transaction, and `block` messages are limited to the maximum /// block size. -impl TrustedPreallocate for Arc { +impl TrustedPreallocate for Transaction { fn max_allocation() -> u64 { // A transparent transaction is the smallest transaction variant MAX_BLOCK_BYTES / MIN_TRANSPARENT_TX_SIZE diff --git a/zebra-chain/src/transaction/tests/preallocate.rs b/zebra-chain/src/transaction/tests/preallocate.rs index 34b72d81..220484b2 100644 --- a/zebra-chain/src/transaction/tests/preallocate.rs +++ b/zebra-chain/src/transaction/tests/preallocate.rs @@ -1,20 +1,20 @@ //! Tests for trusted preallocation during deserialization. -use super::super::{ - serialize::{MIN_TRANSPARENT_INPUT_SIZE, MIN_TRANSPARENT_OUTPUT_SIZE, MIN_TRANSPARENT_TX_SIZE}, - transparent::Input, - transparent::Output, - Transaction, -}; +use proptest::prelude::*; use crate::{ block::MAX_BLOCK_BYTES, - serialization::{TrustedPreallocate, ZcashSerialize}, + serialization::{arbitrary::max_allocation_is_big_enough, TrustedPreallocate, ZcashSerialize}, + transaction::{ + serialize::{ + MIN_TRANSPARENT_INPUT_SIZE, MIN_TRANSPARENT_OUTPUT_SIZE, MIN_TRANSPARENT_TX_SIZE, + }, + transparent::Input, + transparent::Output, + Transaction, + }, }; -use proptest::prelude::*; -use std::{convert::TryInto, sync::Arc}; - proptest! { /// Confirm that each spend takes at least MIN_TRANSPARENT_TX_SIZE bytes when serialized. /// This verifies that our calculated [`TrustedPreallocate::max_allocation`] is indeed an upper bound. @@ -49,55 +49,66 @@ proptest! { /// Verify the smallest disallowed vector of `Transaction`s is too large to fit in a Zcash block #[test] fn tx_max_allocation_is_big_enough(tx in Transaction::arbitrary()) { - - let max_allocation: usize = >::max_allocation().try_into().unwrap(); - let mut smallest_disallowed_vec = Vec::with_capacity(max_allocation + 1); - for _ in 0..(>::max_allocation()+1) { - smallest_disallowed_vec.push(Arc::new(tx.clone())); - } - let serialized = smallest_disallowed_vec.zcash_serialize_to_vec().expect("Serialization to vec must succeed"); + let ( + smallest_disallowed_vec_len, + smallest_disallowed_serialized_len, + largest_allowed_vec_len, + _largest_allowed_serialized_len, + ) = max_allocation_is_big_enough(tx); // Check that our smallest_disallowed_vec is only one item larger than the limit - prop_assert!(((smallest_disallowed_vec.len() - 1) as u64) == >::max_allocation()); - // Check that our smallest_disallowed_vec is too big to be included in a valid block - prop_assert!(serialized.len() as u64 > MAX_BLOCK_BYTES); + prop_assert!(((smallest_disallowed_vec_len - 1) as u64) == Transaction::max_allocation()); + // Check that our smallest_disallowed_vec is too big to send in a valid Zcash Block + prop_assert!(smallest_disallowed_serialized_len as u64 > MAX_BLOCK_BYTES); + + // Check that our largest_allowed_vec contains the maximum number of Transactions + prop_assert!((largest_allowed_vec_len as u64) == Transaction::max_allocation()); + // largest_allowed_serialized_len exceeds the limit for variable-sized types } /// Verify the smallest disallowed vector of `Input`s is too large to fit in a Zcash block #[test] fn input_max_allocation_is_big_enough(input in Input::arbitrary()) { - let max_allocation: usize = Input::max_allocation().try_into().unwrap(); - let mut smallest_disallowed_vec = Vec::with_capacity(max_allocation + 1); - for _ in 0..(Input::max_allocation()+1) { - smallest_disallowed_vec.push(input.clone()); - } - let serialized = smallest_disallowed_vec.zcash_serialize_to_vec().expect("Serialization to vec must succeed"); + let ( + smallest_disallowed_vec_len, + smallest_disallowed_serialized_len, + largest_allowed_vec_len, + _largest_allowed_serialized_len, + ) = max_allocation_is_big_enough(input); // Check that our smallest_disallowed_vec is only one item larger than the limit - prop_assert!(((smallest_disallowed_vec.len() - 1) as u64) == Input::max_allocation()); + prop_assert!(((smallest_disallowed_vec_len - 1) as u64) == Input::max_allocation()); // Check that our smallest_disallowed_vec is too big to be included in a valid block // Note that a serialized block always includes at least one byte for the number of transactions, // so any serialized Vec at least MAX_BLOCK_BYTES long is too large to fit in a block. - prop_assert!(serialized.len() as u64 >= MAX_BLOCK_BYTES); + prop_assert!(smallest_disallowed_serialized_len as u64 >= MAX_BLOCK_BYTES); + + // Check that our largest_allowed_vec contains the maximum number of Inputs + prop_assert!((largest_allowed_vec_len as u64) == Input::max_allocation()); + // largest_allowed_serialized_len exceeds the limit for variable-sized types } /// Verify the smallest disallowed vector of `Output`s is too large to fit in a Zcash block #[test] fn output_max_allocation_is_big_enough(output in Output::arbitrary()) { - let max_allocation: usize = Output::max_allocation().try_into().unwrap(); - let mut smallest_disallowed_vec = Vec::with_capacity(max_allocation + 1); - for _ in 0..(Output::max_allocation()+1) { - smallest_disallowed_vec.push(output.clone()); - } - let serialized = smallest_disallowed_vec.zcash_serialize_to_vec().expect("Serialization to vec must succeed"); + let ( + smallest_disallowed_vec_len, + smallest_disallowed_serialized_len, + largest_allowed_vec_len, + _largest_allowed_serialized_len, + ) = max_allocation_is_big_enough(output); // Check that our smallest_disallowed_vec is only one item larger than the limit - prop_assert!(((smallest_disallowed_vec.len() - 1) as u64) == Output::max_allocation()); + prop_assert!(((smallest_disallowed_vec_len - 1) as u64) == Output::max_allocation()); // Check that our smallest_disallowed_vec is too big to be included in a valid block // Note that a serialized block always includes at least one byte for the number of transactions, // so any serialized Vec at least MAX_BLOCK_BYTES long is too large to fit in a block. - prop_assert!(serialized.len() as u64 >= MAX_BLOCK_BYTES); + prop_assert!(smallest_disallowed_serialized_len as u64 >= MAX_BLOCK_BYTES); + + // Check that our largest_allowed_vec contains the maximum number of Outputs + prop_assert!((largest_allowed_vec_len as u64) == Output::max_allocation()); + // largest_allowed_serialized_len exceeds the limit for variable-sized types } } diff --git a/zebra-network/src/protocol/external/tests/preallocate.rs b/zebra-network/src/protocol/external/tests/preallocate.rs index 54e58c4c..f16fe4fc 100644 --- a/zebra-network/src/protocol/external/tests/preallocate.rs +++ b/zebra-network/src/protocol/external/tests/preallocate.rs @@ -4,13 +4,17 @@ use std::convert::TryInto; use proptest::prelude::*; -use zebra_chain::serialization::{TrustedPreallocate, ZcashSerialize, MAX_PROTOCOL_MESSAGE_LEN}; +use zebra_chain::serialization::{ + arbitrary::max_allocation_is_big_enough, TrustedPreallocate, ZcashSerialize, + MAX_PROTOCOL_MESSAGE_LEN, +}; -use crate::meta_addr::MetaAddr; - -use super::super::{ - addr::{AddrV1, ADDR_V1_SIZE}, - inv::InventoryHash, +use crate::{ + meta_addr::MetaAddr, + protocol::external::{ + addr::{AddrV1, ADDR_V1_SIZE}, + inv::InventoryHash, + }, }; proptest! { @@ -30,7 +34,7 @@ proptest! { InventoryHash::Wtx(_) => 32 + 32 + 4, }; - assert_eq!(serialized_inv.len(), expected_size); + prop_assert_eq!(serialized_inv.len(), expected_size); } /// Verifies that... @@ -39,17 +43,15 @@ proptest! { #[test] fn inv_hash_max_allocation_is_correct(inv in InventoryHash::smallest_types_strategy()) { let max_allocation: usize = InventoryHash::max_allocation().try_into().unwrap(); - let mut smallest_disallowed_vec = Vec::with_capacity(max_allocation + 1); - for _ in 0..(InventoryHash::max_allocation() + 1) { - smallest_disallowed_vec.push(inv); - } + let mut smallest_disallowed_vec = vec![inv; max_allocation + 1]; + let smallest_disallowed_serialized = smallest_disallowed_vec .zcash_serialize_to_vec() .expect("Serialization to vec must succeed"); // Check that our smallest_disallowed_vec is only one item larger than the limit - assert!(((smallest_disallowed_vec.len() - 1) as u64) == InventoryHash::max_allocation()); + prop_assert!(((smallest_disallowed_vec.len() - 1) as u64) == InventoryHash::max_allocation()); // Check that our smallest_disallowed_vec is too big to fit in a Zcash message. - assert!(smallest_disallowed_serialized.len() > MAX_PROTOCOL_MESSAGE_LEN); + prop_assert!(smallest_disallowed_serialized.len() > MAX_PROTOCOL_MESSAGE_LEN); // Create largest_allowed_vec by removing one element from smallest_disallowed_vec without copying (for efficiency) smallest_disallowed_vec.pop(); @@ -59,9 +61,9 @@ proptest! { .expect("Serialization to vec must succeed"); // Check that our largest_allowed_vec contains the maximum number of InventoryHashes - assert!((largest_allowed_vec.len() as u64) == InventoryHash::max_allocation()); + prop_assert!((largest_allowed_vec.len() as u64) == InventoryHash::max_allocation()); // Check that our largest_allowed_vec is small enough to fit in a Zcash message. - assert!(largest_allowed_serialized.len() <= MAX_PROTOCOL_MESSAGE_LEN); + prop_assert!(largest_allowed_serialized.len() <= MAX_PROTOCOL_MESSAGE_LEN); } } @@ -81,7 +83,7 @@ proptest! { let serialized = addr .zcash_serialize_to_vec() .expect("Serialization to vec must succeed"); - assert!(serialized.len() == ADDR_V1_SIZE) + prop_assert!(serialized.len() == ADDR_V1_SIZE) } /// Verifies that... @@ -97,29 +99,21 @@ proptest! { let addr: AddrV1 = addr.unwrap().into(); - let max_allocation: usize = AddrV1::max_allocation().try_into().unwrap(); - let mut smallest_disallowed_vec = Vec::with_capacity(max_allocation + 1); - for _ in 0..(AddrV1::max_allocation() + 1) { - smallest_disallowed_vec.push(addr); - } - let smallest_disallowed_serialized = smallest_disallowed_vec - .zcash_serialize_to_vec() - .expect("Serialization to vec must succeed"); - // Check that our smallest_disallowed_vec is only one item larger than the limit - assert!(((smallest_disallowed_vec.len() - 1) as u64) == AddrV1::max_allocation()); - // Check that our smallest_disallowed_vec is too big to send in a valid Zcash message - assert!(smallest_disallowed_serialized.len() > MAX_PROTOCOL_MESSAGE_LEN); + let ( + smallest_disallowed_vec_len, + smallest_disallowed_serialized_len, + largest_allowed_vec_len, + largest_allowed_serialized_len, + ) = max_allocation_is_big_enough(addr); - // Create largest_allowed_vec by removing one element from smallest_disallowed_vec without copying (for efficiency) - smallest_disallowed_vec.pop(); - let largest_allowed_vec = smallest_disallowed_vec; - let largest_allowed_serialized = largest_allowed_vec - .zcash_serialize_to_vec() - .expect("Serialization to vec must succeed"); + // Check that our smallest_disallowed_vec is only one item larger than the limit + prop_assert!(((smallest_disallowed_vec_len - 1) as u64) == AddrV1::max_allocation()); + // Check that our smallest_disallowed_vec is too big to send in a valid Zcash message + prop_assert!(smallest_disallowed_serialized_len > MAX_PROTOCOL_MESSAGE_LEN); // Check that our largest_allowed_vec contains the maximum number of AddrV1s - assert!((largest_allowed_vec.len() as u64) == AddrV1::max_allocation()); + prop_assert!((largest_allowed_vec_len as u64) == AddrV1::max_allocation()); // Check that our largest_allowed_vec is small enough to fit in a Zcash message. - assert!(largest_allowed_serialized.len() <= MAX_PROTOCOL_MESSAGE_LEN); + prop_assert!(largest_allowed_serialized_len <= MAX_PROTOCOL_MESSAGE_LEN); } }