diff --git a/zebra-state/src/sled_state/sled_format.rs b/zebra-state/src/sled_state/sled_format.rs index 2a72e29a1..a95b566a5 100644 --- a/zebra-state/src/sled_state/sled_format.rs +++ b/zebra-state/src/sled_state/sled_format.rs @@ -9,6 +9,7 @@ use zebra_chain::{ sprout, transaction, transparent, }; +#[derive(Debug, Clone, Copy, PartialEq)] pub struct TransactionLocation { pub height: block::Height, pub index: u32, @@ -123,6 +124,13 @@ impl IntoSled for block::Hash { } } +impl FromSled for block::Hash { + fn from_ivec(bytes: sled::IVec) -> Self { + let array = bytes.as_ref().try_into().unwrap(); + Self(array) + } +} + impl IntoSled for &sprout::Nullifier { type Bytes = [u8; 32]; @@ -159,13 +167,6 @@ impl IntoSled for () { } } -impl FromSled for block::Hash { - fn from_ivec(bytes: sled::IVec) -> Self { - let array = bytes.as_ref().try_into().unwrap(); - Self(array) - } -} - impl IntoSled for block::Height { type Bytes = [u8; 4]; @@ -274,3 +275,145 @@ impl SledDeserialize for sled::Tree { value_bytes.map(V::from_ivec) } } + +#[cfg(test)] +mod tests { + use super::*; + use proptest::{arbitrary::any, prelude::*}; + use std::ops::Deref; + + impl Arbitrary for TransactionLocation { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + (any::(), any::()) + .prop_map(|(height, index)| Self { height, index }) + .boxed() + } + + type Strategy = BoxedStrategy; + } + + fn round_trip(input: T) -> T + where + T: IntoSled + FromSled, + { + let bytes = input.into_ivec(); + T::from_ivec(bytes) + } + + /// The round trip test covers types that are used as value field in a sled + /// Tree. Only these types are ever deserialized, and so they're the only + /// ones that implement both `IntoSled` and `FromSled`. + fn assert_round_trip(input: T) + where + T: IntoSled + FromSled + Clone + PartialEq + std::fmt::Debug, + { + let before = input.clone(); + let after = round_trip(input); + assert_eq!(before, after); + } + + /// This test asserts that types that are used as sled keys behave correctly. + /// Any type that implements `IntoIVec` can be used as a sled key. The value + /// is serialized via `IntoSled::into_ivec` when the `key`, `value` pair is + /// inserted into the sled tree. The `as_bytes` impl on the other hand is + /// called for most other operations when comparing a key against existing + /// keys in the sled database, such as `contains`. + fn assert_as_bytes_matches_ivec(input: T) + where + T: IntoSled + Clone, + { + let before = input.clone(); + let ivec = input.into_ivec(); + assert_eq!(before.as_bytes().as_ref(), ivec.as_ref()); + } + + #[test] + fn roundtrip_transaction_location() { + zebra_test::init(); + proptest!(|(val in any::())| assert_round_trip(val)); + } + + #[test] + fn roundtrip_block_hash() { + zebra_test::init(); + proptest!(|(val in any::())| assert_round_trip(val)); + } + + #[test] + fn roundtrip_block_height() { + zebra_test::init(); + proptest!(|(val in any::())| assert_round_trip(val)); + } + + #[test] + fn roundtrip_block() { + zebra_test::init(); + + proptest!(|(block in any::())| { + let bytes = block.into_ivec(); + let deserialized: Arc = FromSled::from_ivec(bytes); + assert_eq!(&block, deserialized.deref()); + }); + } + + #[test] + fn roundtrip_transparent_output() { + zebra_test::init(); + + proptest!(|(block in any::())| { + let bytes = block.into_ivec(); + let deserialized: transparent::Output = FromSled::from_ivec(bytes); + assert_eq!(block, deserialized); + }); + } + + #[test] + fn key_matches_ivec_transaction_location() { + zebra_test::init(); + proptest!(|(val in any::())| assert_as_bytes_matches_ivec(val)); + } + + #[test] + fn key_matches_ivec_trans_hash() { + zebra_test::init(); + proptest!(|(val in any::())| assert_as_bytes_matches_ivec(val)); + } + + #[test] + fn key_matches_ivec_block_hash() { + zebra_test::init(); + proptest!(|(val in any::())| assert_as_bytes_matches_ivec(val)); + } + + #[test] + fn key_matches_ivec_sprout_nullifier() { + zebra_test::init(); + proptest!(|(val in any::())| assert_as_bytes_matches_ivec(&val)); + } + + #[test] + fn key_matches_ivec_sapling_nullifier() { + zebra_test::init(); + proptest!(|(val in any::())| assert_as_bytes_matches_ivec(&val)); + } + + #[test] + fn key_matches_ivec_block_height() { + zebra_test::init(); + proptest!(|(val in any::())| assert_as_bytes_matches_ivec(val)); + } + + #[test] + fn key_matches_ivec_transparent_output() { + zebra_test::init(); + proptest!(|(val in any::())| assert_as_bytes_matches_ivec(&val)); + } + + #[test] + fn key_matches_ivec_transparent_outpoint() { + zebra_test::init(); + proptest!(|(val in any::())| assert_as_bytes_matches_ivec(val)); + } +}