From f0549b2f7c8440cf15288a00f3370abbd8f3fcad Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 25 May 2021 01:10:07 +1000 Subject: [PATCH] Derive Arbitrary impls for a bunch of chain and network types (#2179) Enable proptests for internal and external network protocol messages, using times with the correct protocol-specific ranges. (4 or 8 bytes.) --- zebra-chain/src/block/arbitrary.rs | 9 +++-- zebra-chain/src/block/header.rs | 4 +++ zebra-chain/src/serialization.rs | 3 ++ zebra-chain/src/serialization/arbitrary.rs | 33 +++++++++++++++++++ zebra-chain/src/sprout/tree.rs | 2 +- zebra-network/Cargo.toml | 1 + .../src/protocol/external/message.rs | 13 ++++++++ zebra-network/src/protocol/external/types.rs | 6 +++- .../src/protocol/internal/request.rs | 4 +++ .../src/protocol/internal/response.rs | 5 +++ 10 files changed, 73 insertions(+), 7 deletions(-) create mode 100644 zebra-chain/src/serialization/arbitrary.rs diff --git a/zebra-chain/src/block/arbitrary.rs b/zebra-chain/src/block/arbitrary.rs index 7a34755aaab..2c87ce96ed4 100644 --- a/zebra-chain/src/block/arbitrary.rs +++ b/zebra-chain/src/block/arbitrary.rs @@ -3,11 +3,11 @@ use proptest::{ prelude::*, }; -use chrono::{TimeZone, Utc}; use std::sync::Arc; use crate::{ parameters::{Network, NetworkUpgrade}, + serialization, work::{difficulty::CompactDifficulty, equihash}, }; @@ -186,8 +186,7 @@ impl Arbitrary for Header { any::(), any::(), any::<[u8; 32]>(), - // time is interpreted as u32 in the spec, but rust timestamps are i64 - (0i64..(u32::MAX as i64)), + serialization::arbitrary::datetime_u32(), any::(), any::<[u8; 32]>(), any::(), @@ -198,7 +197,7 @@ impl Arbitrary for Header { previous_block_hash, merkle_root, commitment_bytes, - timestamp, + time, difficulty_threshold, nonce, solution, @@ -207,7 +206,7 @@ impl Arbitrary for Header { previous_block_hash, merkle_root, commitment_bytes, - time: Utc.timestamp(timestamp, 0), + time, difficulty_threshold, nonce, solution, diff --git a/zebra-chain/src/block/header.rs b/zebra-chain/src/block/header.rs index 48c10efbe43..708be93b228 100644 --- a/zebra-chain/src/block/header.rs +++ b/zebra-chain/src/block/header.rs @@ -10,6 +10,9 @@ use crate::{ use super::{merkle, Hash, Height}; +#[cfg(any(test, feature = "proptest-impl"))] +use proptest_derive::Arbitrary; + /// A block header, containing metadata about a block. /// /// How are blocks chained together? They are chained together via the @@ -121,6 +124,7 @@ impl Header { /// /// This structure is used in the Bitcoin network protocol. #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] pub struct CountedHeader { pub header: Header, pub transaction_count: usize, diff --git a/zebra-chain/src/serialization.rs b/zebra-chain/src/serialization.rs index 693879b2750..e384cfa6f1e 100644 --- a/zebra-chain/src/serialization.rs +++ b/zebra-chain/src/serialization.rs @@ -17,6 +17,9 @@ pub(crate) mod serde_helpers; pub mod sha256d; +#[cfg(any(test, feature = "proptest-impl"))] +pub mod arbitrary; + pub use constraint::AtLeastOne; pub use error::SerializationError; pub use read_zcash::ReadZcashExt; diff --git a/zebra-chain/src/serialization/arbitrary.rs b/zebra-chain/src/serialization/arbitrary.rs new file mode 100644 index 00000000000..71e8cdd67e1 --- /dev/null +++ b/zebra-chain/src/serialization/arbitrary.rs @@ -0,0 +1,33 @@ +use chrono::{TimeZone, Utc, MAX_DATETIME, MIN_DATETIME}; +use proptest::{arbitrary::any, prelude::*}; + +/// Returns a strategy that produces an arbitrary [`chrono::DateTime`], +/// based on the full valid range of the type. +/// +/// Both the seconds and nanoseconds values are randomised. But leap nanoseconds +/// are never present. +/// https://docs.rs/chrono/0.4.19/chrono/struct.DateTime.html#method.timestamp_subsec_nanos +/// +/// Zebra uses these times internally, via [`Utc::now`]. +/// +/// Some parts of the Zcash network protocol ([`Version`] messages) also use times +/// with an 8-byte seconds value. Unlike this function, they have zero +/// nanoseconds values. +pub fn datetime_full() -> impl Strategy> { + ( + MIN_DATETIME.timestamp()..=MAX_DATETIME.timestamp(), + 0..1_000_000_000_u32, + ) + .prop_map(|(secs, nsecs)| Utc.timestamp(secs, nsecs)) +} + +/// Returns a strategy that produces an arbitrary time from a [`u32`] number +/// of seconds past the epoch. +/// +/// The nanoseconds value is always zero. +/// +/// The Zcash protocol typically uses 4-byte seconds values, except for the +/// [`Version`] message. +pub fn datetime_u32() -> impl Strategy> { + any::().prop_map(|secs| Utc.timestamp(secs.into(), 0)) +} diff --git a/zebra-chain/src/sprout/tree.rs b/zebra-chain/src/sprout/tree.rs index 4399c31feab..f8672c73775 100644 --- a/zebra-chain/src/sprout/tree.rs +++ b/zebra-chain/src/sprout/tree.rs @@ -106,7 +106,7 @@ impl From for [u8; 32] { /// Sprout Note Commitment Tree #[derive(Clone, Debug, Default, Eq, PartialEq)] -#[cfg_attr(test, derive(Arbitrary))] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] struct NoteCommitmentTree { /// The root node of the tree (often used as an anchor). root: Root, diff --git a/zebra-network/Cargo.toml b/zebra-network/Cargo.toml index 620dff4f8f9..d0d845319a2 100644 --- a/zebra-network/Cargo.toml +++ b/zebra-network/Cargo.toml @@ -40,4 +40,5 @@ proptest = "0.10" proptest-derive = "0.3" toml = "0.5" +zebra-chain = { path = "../zebra-chain", features = ["proptest-impl"] } zebra-test = { path = "../zebra-test/" } diff --git a/zebra-network/src/protocol/external/message.rs b/zebra-network/src/protocol/external/message.rs index 60d4ae1d423..e3a75d9473e 100644 --- a/zebra-network/src/protocol/external/message.rs +++ b/zebra-network/src/protocol/external/message.rs @@ -14,6 +14,11 @@ use super::inv::InventoryHash; use super::types::*; use crate::meta_addr::MetaAddr; +#[cfg(any(test, feature = "proptest-impl"))] +use proptest_derive::Arbitrary; +#[cfg(any(test, feature = "proptest-impl"))] +use zebra_chain::serialization::arbitrary::datetime_full; + /// A Bitcoin-like network message for the Zcash protocol. /// /// The Zcash network protocol is mostly inherited from Bitcoin, and a list of @@ -31,6 +36,7 @@ use crate::meta_addr::MetaAddr; /// /// [btc_wiki_protocol]: https://en.bitcoin.it/wiki/Protocol_documentation #[derive(Clone, Eq, PartialEq, Debug)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] pub enum Message { /// A `version` message. /// @@ -47,6 +53,12 @@ pub enum Message { services: PeerServices, /// The time when the version message was sent. + /// + /// This is a 64-bit field. Zebra rejects out-of-range times as invalid. + #[cfg_attr( + any(test, feature = "proptest-impl"), + proptest(strategy = "datetime_full()") + )] timestamp: DateTime, /// The network address of the node receiving this message, and its @@ -307,6 +319,7 @@ where /// /// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#reject) #[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] #[repr(u8)] #[allow(missing_docs)] pub enum RejectReason { diff --git a/zebra-network/src/protocol/external/types.rs b/zebra-network/src/protocol/external/types.rs index d1b6b320221..0a5658f004e 100644 --- a/zebra-network/src/protocol/external/types.rs +++ b/zebra-network/src/protocol/external/types.rs @@ -17,7 +17,7 @@ use proptest_derive::Arbitrary; /// A magic number identifying the network. #[derive(Copy, Clone, Eq, PartialEq)] -#[cfg_attr(test, derive(Arbitrary))] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] pub struct Magic(pub [u8; 4]); impl fmt::Debug for Magic { @@ -38,6 +38,7 @@ impl From for Magic { /// A protocol version number. #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] pub struct Version(pub u32); impl Version { @@ -89,6 +90,7 @@ bitflags! { /// A nonce used in the networking layer to identify messages. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] pub struct Nonce(pub u64); impl Default for Nonce { @@ -100,6 +102,7 @@ impl Default for Nonce { /// A random value to add to the seed value in a hash function. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] pub struct Tweak(pub u32); impl Default for Tweak { @@ -112,6 +115,7 @@ impl Default for Tweak { /// A Bloom filter consisting of a bit field of arbitrary byte-aligned /// size, maximum size is 36,000 bytes. #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] pub struct Filter(pub Vec); #[cfg(test)] diff --git a/zebra-network/src/protocol/internal/request.rs b/zebra-network/src/protocol/internal/request.rs index 364f0a0d543..a31f7aeaa63 100644 --- a/zebra-network/src/protocol/internal/request.rs +++ b/zebra-network/src/protocol/internal/request.rs @@ -7,6 +7,9 @@ use zebra_chain::{ use super::super::types::Nonce; +#[cfg(any(test, feature = "proptest-impl"))] +use proptest_derive::Arbitrary; + /// A network request, represented in internal format. /// /// The network layer aims to abstract away the details of the Bitcoin wire @@ -27,6 +30,7 @@ use super::super::types::Nonce; /// a best-effort attempt to ignore any messages responsive to the cancelled /// request, subject to limitations in the underlying Zcash protocol. #[derive(Clone, Debug)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] pub enum Request { /// Requests additional peers from the server. /// diff --git a/zebra-network/src/protocol/internal/response.rs b/zebra-network/src/protocol/internal/response.rs index 130f81a7e85..4cb3c17b52c 100644 --- a/zebra-network/src/protocol/internal/response.rs +++ b/zebra-network/src/protocol/internal/response.rs @@ -4,10 +4,15 @@ use zebra_chain::{ }; use crate::meta_addr::MetaAddr; + use std::sync::Arc; +#[cfg(any(test, feature = "proptest-impl"))] +use proptest_derive::Arbitrary; + /// A response to a network request, represented in internal format. #[derive(Clone, Debug)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] pub enum Response { /// Do not send any response to this request. ///