zebra_chain/serialization/arbitrary.rs
1//! Arbitrary data generation for serialization proptests
2
3use chrono::{DateTime, TimeZone, Utc};
4use proptest::prelude::*;
5
6use super::{
7 CompactSizeMessage, DateTime32, TrustedPreallocate, ZcashSerialize, MAX_PROTOCOL_MESSAGE_LEN,
8};
9
10impl Arbitrary for DateTime32 {
11 type Parameters = ();
12
13 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
14 any::<u32>().prop_map(Into::into).boxed()
15 }
16
17 type Strategy = BoxedStrategy<Self>;
18}
19
20/// Returns a strategy that produces an arbitrary [`chrono::DateTime<Utc>`],
21/// based on the full valid range of the type.
22///
23/// Both the seconds and nanoseconds values are randomised, including leap seconds:
24/// <https://docs.rs/chrono/0.4.19/chrono/naive/struct.NaiveTime.html#leap-second-handling>
25///
26/// Wherever possible, Zebra should handle leap seconds by:
27/// - making durations and intervals 3 seconds or longer,
28/// - avoiding complex time-based calculations, and
29/// - avoiding relying on subsecond precision or time order.
30///
31/// When monotonic times are needed, use the opaque `std::time::Instant` type.
32///
33/// # Usage
34///
35/// Zebra uses these times internally, typically via [`Utc::now`].
36///
37/// Some parts of the Zcash network protocol (`Version` messages) also use times
38/// with an 8-byte seconds value. Unlike this function, they have zero
39/// nanoseconds values. (So they never have `chrono` leap seconds.)
40pub fn datetime_full() -> impl Strategy<Value = chrono::DateTime<Utc>> {
41 (
42 // TODO: should we be subtracting 1 from the maximum timestamp?
43 DateTime::<Utc>::MIN_UTC.timestamp()..=DateTime::<Utc>::MAX_UTC.timestamp(),
44 // > The nanosecond part can exceed 1,000,000,000 in order to represent a leap second,
45 // > but only when secs % 60 == 59. (The true "UNIX timestamp" cannot represent a leap second
46 // > unambiguously.)
47 //
48 // https://docs.rs/chrono/latest/chrono/offset/trait.TimeZone.html#method.timestamp_opt
49 //
50 // We use `1_000_000_000` as the right side of the range to avoid that issue.
51 0..1_000_000_000_u32,
52 )
53 .prop_map(|(secs, nsecs)| {
54 Utc.timestamp_opt(secs, nsecs)
55 .single()
56 .expect("in-range number of seconds and valid nanosecond")
57 })
58}
59
60/// Returns a strategy that produces an arbitrary time from a [`u32`] number
61/// of seconds past the epoch.
62///
63/// The nanoseconds value is always zero.
64///
65/// The Zcash protocol typically uses 4-byte seconds values, except for the
66/// `Version` message.
67///
68/// TODO: replace this strategy with `any::<DateTime32>()`.
69pub fn datetime_u32() -> impl Strategy<Value = chrono::DateTime<Utc>> {
70 any::<DateTime32>().prop_map(Into::into)
71}
72
73impl Arbitrary for CompactSizeMessage {
74 type Parameters = ();
75
76 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
77 (0..=MAX_PROTOCOL_MESSAGE_LEN)
78 .prop_map(|size| {
79 size.try_into()
80 .expect("MAX_PROTOCOL_MESSAGE_LEN fits in CompactSizeMessage")
81 })
82 .boxed()
83 }
84
85 type Strategy = BoxedStrategy<Self>;
86}
87
88/// Allocate a maximum-sized vector of cloned `item`s, and serialize that array.
89///
90/// Returns the following calculations on `item`:
91/// smallest_disallowed_vec_len
92/// smallest_disallowed_serialized_len
93/// largest_allowed_vec_len
94/// largest_allowed_serialized_len
95///
96/// For varible-size types, `largest_allowed_serialized_len` might not fit within
97/// `MAX_PROTOCOL_MESSAGE_LEN` or `MAX_BLOCK_SIZE`.
98pub fn max_allocation_is_big_enough<T>(item: T) -> (usize, usize, usize, usize)
99where
100 T: TrustedPreallocate + ZcashSerialize + Clone,
101{
102 let max_allocation: usize = T::max_allocation().try_into().unwrap();
103 let mut smallest_disallowed_vec = vec![item; max_allocation + 1];
104
105 let smallest_disallowed_serialized = smallest_disallowed_vec
106 .zcash_serialize_to_vec()
107 .expect("Serialization to vec must succeed");
108 let smallest_disallowed_vec_len = smallest_disallowed_vec.len();
109
110 // Create largest_allowed_vec by removing one element from smallest_disallowed_vec without copying (for efficiency)
111 smallest_disallowed_vec.pop();
112 let largest_allowed_vec = smallest_disallowed_vec;
113 let largest_allowed_serialized = largest_allowed_vec
114 .zcash_serialize_to_vec()
115 .expect("Serialization to vec must succeed");
116
117 (
118 smallest_disallowed_vec_len,
119 smallest_disallowed_serialized.len(),
120 largest_allowed_vec.len(),
121 largest_allowed_serialized.len(),
122 )
123}