diff --git a/zebra-chain/src/block/height.rs b/zebra-chain/src/block/height.rs index c5fb22efd..7449a5100 100644 --- a/zebra-chain/src/block/height.rs +++ b/zebra-chain/src/block/height.rs @@ -23,6 +23,7 @@ pub mod json_conversion; /// There are multiple formats for serializing a height, so we don't implement /// `ZcashSerialize` or `ZcashDeserialize` for `Height`. #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Default))] pub struct Height(pub u32); #[derive(Error, Debug)] diff --git a/zebra-state/src/service/finalized_state/disk_format/block.rs b/zebra-state/src/service/finalized_state/disk_format/block.rs index 7069e19f8..22495ebf3 100644 --- a/zebra-state/src/service/finalized_state/disk_format/block.rs +++ b/zebra-state/src/service/finalized_state/disk_format/block.rs @@ -65,7 +65,7 @@ pub const TRANSACTION_LOCATION_DISK_BYTES: usize = HEIGHT_DISK_BYTES + TX_INDEX_ #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] #[cfg_attr( any(test, feature = "proptest-impl"), - derive(Arbitrary, Serialize, Deserialize) + derive(Arbitrary, Default, Serialize, Deserialize) )] pub struct TransactionIndex(pub(super) u16); @@ -126,7 +126,7 @@ impl TransactionIndex { #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] #[cfg_attr( any(test, feature = "proptest-impl"), - derive(Arbitrary, Serialize, Deserialize) + derive(Arbitrary, Default, Serialize, Deserialize) )] pub struct TransactionLocation { /// The block height of the transaction. diff --git a/zebra-state/src/service/finalized_state/disk_format/scan.rs b/zebra-state/src/service/finalized_state/disk_format/scan.rs index c6a47ebc7..4d4b3b813 100644 --- a/zebra-state/src/service/finalized_state/disk_format/scan.rs +++ b/zebra-state/src/service/finalized_state/disk_format/scan.rs @@ -13,6 +13,12 @@ use crate::{FromDisk, IntoDisk, TransactionLocation}; use super::block::TRANSACTION_LOCATION_DISK_BYTES; +#[cfg(any(test, feature = "proptest-impl"))] +use proptest_derive::Arbitrary; + +#[cfg(test)] +mod tests; + /// The type used in Zebra to store Sapling scanning keys. /// It can represent a full viewing key or an individual viewing key. pub type SaplingScanningKey = String; @@ -22,6 +28,7 @@ pub type SaplingScanningKey = String; /// Currently contains a TXID in "display order", which is big-endian byte order following the u256 /// convention set by Bitcoin and zcashd. #[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary, Default))] pub struct SaplingScannedResult([u8; 32]); impl From for transaction::Hash { @@ -38,6 +45,7 @@ impl From<&[u8; 32]> for SaplingScannedResult { /// A database column family entry for a block scanned with a Sapling vieweing key. #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary, Default))] pub struct SaplingScannedDatabaseEntry { /// The database column family key. Must be unique for each scanning key and scanned block. pub index: SaplingScannedDatabaseIndex, @@ -48,6 +56,7 @@ pub struct SaplingScannedDatabaseEntry { /// A database column family key for a block scanned with a Sapling vieweing key. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary, Default))] pub struct SaplingScannedDatabaseIndex { /// The Sapling viewing key used to scan the block. pub sapling_key: SaplingScanningKey, diff --git a/zebra-state/src/service/finalized_state/disk_format/scan/tests.rs b/zebra-state/src/service/finalized_state/disk_format/scan/tests.rs new file mode 100644 index 000000000..0aa4c9389 --- /dev/null +++ b/zebra-state/src/service/finalized_state/disk_format/scan/tests.rs @@ -0,0 +1,3 @@ +//! Tests for scanner database serialization. + +mod prop; diff --git a/zebra-state/src/service/finalized_state/disk_format/scan/tests/prop.rs b/zebra-state/src/service/finalized_state/disk_format/scan/tests/prop.rs new file mode 100644 index 000000000..fa964370b --- /dev/null +++ b/zebra-state/src/service/finalized_state/disk_format/scan/tests/prop.rs @@ -0,0 +1,44 @@ +//! Randomised proptests for scanner database formats. + +use proptest::{arbitrary::any, prelude::*}; + +use crate::{ + service::finalized_state::arbitrary::assert_value_properties, SaplingScannedDatabaseIndex, + SaplingScannedResult, SaplingScanningKey, MAX_ON_DISK_HEIGHT, +}; + +#[test] +fn roundtrip_sapling_scanning_key() { + let _init_guard = zebra_test::init(); + + proptest!(|(val in any::())| assert_value_properties(val)); +} + +#[test] +fn roundtrip_sapling_db_index() { + let _init_guard = zebra_test::init(); + + proptest!( + |(mut val in any::())| { + // Limit the random height to the valid on-disk range. + // Blocks outside this range are rejected before they reach the state. + // (It would take decades to generate a valid chain this high.) + val.tx_loc.height.0 %= MAX_ON_DISK_HEIGHT.0 + 1; + assert_value_properties(val) + } + ); +} + +#[test] +fn roundtrip_sapling_result() { + let _init_guard = zebra_test::init(); + + proptest!(|(val in any::())| assert_value_properties(val)); +} + +#[test] +fn roundtrip_option_sapling_result() { + let _init_guard = zebra_test::init(); + + proptest!(|(val in any::>())| assert_value_properties(val)); +} diff --git a/zebra-state/src/service/finalized_state/disk_format/tests/prop.rs b/zebra-state/src/service/finalized_state/disk_format/tests/prop.rs index a76f9efff..5067bcfe7 100644 --- a/zebra-state/src/service/finalized_state/disk_format/tests/prop.rs +++ b/zebra-state/src/service/finalized_state/disk_format/tests/prop.rs @@ -26,12 +26,15 @@ use crate::service::finalized_state::{ // Common -// TODO: turn this into a unit test, it has a fixed value +/// This test has a fixed value, so testing it once is sufficient. #[test] fn roundtrip_unit_type() { let _init_guard = zebra_test::init(); - proptest!(|(val in any::<()>())| assert_value_properties(val)); + // The unit type `()` is serialized to the empty (zero-length) array `[]`. + #[allow(clippy::let_unit_value)] + let value = (); + assert_value_properties(value); } // Block @@ -46,7 +49,7 @@ fn roundtrip_block_height() { // Limit the random height to the valid on-disk range. // Blocks outside this range are rejected before they reach the state. // (It would take decades to generate a valid chain this high.) - val = val.clamp(Height(0), MAX_ON_DISK_HEIGHT); + val.0 %= MAX_ON_DISK_HEIGHT.0 + 1; assert_value_properties(val) } ); @@ -74,7 +77,7 @@ fn roundtrip_transaction_location() { proptest!( |(mut val in any::())| { - val.height = val.height.clamp(Height(0), MAX_ON_DISK_HEIGHT); + val.height.0 %= MAX_ON_DISK_HEIGHT.0 + 1; assert_value_properties(val) } ); @@ -142,7 +145,7 @@ fn roundtrip_output_location() { proptest!( |(mut val in any::())| { - *val.height_mut() = val.height().clamp(Height(0), MAX_ON_DISK_HEIGHT); + val.height_mut().0 %= MAX_ON_DISK_HEIGHT.0 + 1; assert_value_properties(val) } ); @@ -154,7 +157,7 @@ fn roundtrip_address_location() { proptest!( |(mut val in any::())| { - *val.height_mut() = val.height().clamp(Height(0), MAX_ON_DISK_HEIGHT); + val.height_mut().0 %= MAX_ON_DISK_HEIGHT.0 + 1; assert_value_properties(val) } ); @@ -166,7 +169,7 @@ fn roundtrip_address_balance_location() { proptest!( |(mut val in any::())| { - *val.height_mut() = val.address_location().height().clamp(Height(0), MAX_ON_DISK_HEIGHT); + val.height_mut().0 %= MAX_ON_DISK_HEIGHT.0 + 1; assert_value_properties(val) } ); @@ -185,8 +188,8 @@ fn roundtrip_address_unspent_output() { proptest!( |(mut val in any::())| { - *val.address_location_mut().height_mut() = val.address_location().height().clamp(Height(0), MAX_ON_DISK_HEIGHT); - *val.unspent_output_location_mut().height_mut() = val.unspent_output_location().height().clamp(Height(0), MAX_ON_DISK_HEIGHT); + val.address_location_mut().height_mut().0 %= MAX_ON_DISK_HEIGHT.0 + 1; + val.unspent_output_location_mut().height_mut().0 %= MAX_ON_DISK_HEIGHT.0 + 1; assert_value_properties(val) } @@ -199,8 +202,8 @@ fn roundtrip_address_transaction() { proptest!( |(mut val in any::())| { - *val.address_location_mut().height_mut() = val.address_location().height().clamp(Height(0), MAX_ON_DISK_HEIGHT); - val.transaction_location_mut().height = val.transaction_location().height.clamp(Height(0), MAX_ON_DISK_HEIGHT); + val.address_location_mut().height_mut().0 %= MAX_ON_DISK_HEIGHT.0 + 1; + val.transaction_location_mut().height.0 %= MAX_ON_DISK_HEIGHT.0 + 1; assert_value_properties(val) } @@ -461,7 +464,6 @@ fn roundtrip_orchard_subtree_data() { let _init_guard = zebra_test::init(); proptest!(|(mut val in any::>())| { - val.end_height = val.end_height.clamp(Height(0), MAX_ON_DISK_HEIGHT); val.end_height.0 %= MAX_ON_DISK_HEIGHT.0 + 1; assert_value_properties(val) });