Proptest `MetaAddr` sanitization and serialization together

This commit is contained in:
teor 2021-05-26 15:36:25 +10:00 committed by Deirdre Connolly
parent 9f8b4f836e
commit 5cdcc5255f
3 changed files with 87 additions and 17 deletions

View File

@ -2,13 +2,13 @@
use super::super::MetaAddr;
use crate::constants::TIMESTAMP_TRUNCATION_SECONDS;
/// Make sure that the sanitize function reduces time and state metadata
/// leaks.
pub(crate) fn sanitize_avoids_leaks(entry: &MetaAddr) {
let sanitized = entry.sanitize();
use crate::{constants::TIMESTAMP_TRUNCATION_SECONDS, types::PeerServices};
/// Check that `sanitized_addr` has less time and state metadata than
/// `original_addr`.
///
/// Also check that the time hasn't changed too much.
pub(crate) fn sanitize_avoids_leaks(original: &MetaAddr, sanitized: &MetaAddr) {
// We want the sanitized timestamp to:
// - be a multiple of the truncation interval,
// - have a zero nanoseconds component, and
@ -20,11 +20,11 @@ pub(crate) fn sanitize_avoids_leaks(entry: &MetaAddr) {
assert_eq!(sanitized.get_last_seen().timestamp_subsec_nanos(), 0);
// handle underflow and overflow by skipping the check
// the other check will ensure correctness
let lowest_time = entry
let lowest_time = original
.get_last_seen()
.timestamp()
.checked_sub(TIMESTAMP_TRUNCATION_SECONDS);
let highest_time = entry
let highest_time = original
.get_last_seen()
.timestamp()
.checked_add(TIMESTAMP_TRUNCATION_SECONDS);
@ -37,9 +37,9 @@ pub(crate) fn sanitize_avoids_leaks(entry: &MetaAddr) {
// Sanitize to the the default state, even though it's not serialized
assert_eq!(sanitized.last_connection_state, Default::default());
// Sanitize to known flags
assert_eq!(sanitized.services, original.services & PeerServices::all());
// We want the other fields to be unmodified
assert_eq!(sanitized.addr, entry.addr);
// Services are sanitized during parsing, so we don't need to make
// any changes in sanitize()
assert_eq!(sanitized.services, entry.services);
assert_eq!(sanitized.addr, original.addr);
}

View File

@ -1,6 +1,7 @@
//! Randomised property tests for MetaAddr.
use super::{super::MetaAddr, check};
use super::check;
use crate::{meta_addr::MetaAddr, types::PeerServices};
use proptest::prelude::*;
@ -10,10 +11,10 @@ proptest! {
/// Make sure that the sanitize function reduces time and state metadata
/// leaks.
#[test]
fn sanitized_fields(entry in MetaAddr::arbitrary()) {
fn sanitize_avoids_leaks(addr in MetaAddr::arbitrary()) {
zebra_test::init();
check::sanitize_avoids_leaks(&entry);
check::sanitize_avoids_leaks(&addr, &addr.sanitize());
}
/// Test round-trip serialization for gossiped MetaAddrs
@ -79,4 +80,73 @@ proptest! {
);
}
/// Test round-trip serialization for all MetaAddr variants after sanitization
#[test]
fn sanitized_roundtrip(
addr in any::<MetaAddr>()
) {
zebra_test::init();
let sanitized_addr = addr.sanitize();
// Make sure sanitization avoids leaks on this address, to avoid spurious errors
check::sanitize_avoids_leaks(&addr, &sanitized_addr);
// Check that sanitization doesn't make Zebra's serialization fail
let addr_bytes = sanitized_addr.zcash_serialize_to_vec();
prop_assert!(
addr_bytes.is_ok(),
"unexpected serialization error: {:?}, addr: {:?}",
addr_bytes,
sanitized_addr
);
let addr_bytes = addr_bytes.unwrap();
// Assume other implementations deserialize like Zebra
let deserialized_addr = MetaAddr::zcash_deserialize(addr_bytes.as_slice());
prop_assert!(
deserialized_addr.is_ok(),
"unexpected deserialization error: {:?}, addr: {:?}, bytes: {:?}",
deserialized_addr,
sanitized_addr,
hex::encode(addr_bytes),
);
let deserialized_addr = deserialized_addr.unwrap();
// Check that the addrs are equal
prop_assert_eq!(
sanitized_addr,
deserialized_addr,
"unexpected round-trip mismatch with bytes: {:?}",
hex::encode(addr_bytes),
);
// Check that serialization hasn't de-sanitized anything
check::sanitize_avoids_leaks(&addr, &deserialized_addr);
// Now check that the re-serialized bytes are equal
// (`impl PartialEq for MetaAddr` might not match serialization equality)
let addr_bytes2 = deserialized_addr.zcash_serialize_to_vec();
prop_assert!(
addr_bytes2.is_ok(),
"unexpected serialization error after round-trip: {:?}, original addr: {:?}, bytes: {:?}, deserialized addr: {:?}",
addr_bytes2,
sanitized_addr,
hex::encode(addr_bytes),
deserialized_addr,
);
let addr_bytes2 = addr_bytes2.unwrap();
prop_assert_eq!(
&addr_bytes,
&addr_bytes2,
"unexpected round-trip bytes mismatch: original addr: {:?}, bytes: {:?}, deserialized addr: {:?}, bytes: {:?}",
sanitized_addr,
hex::encode(&addr_bytes),
deserialized_addr,
hex::encode(&addr_bytes2),
);
}
}

View File

@ -23,6 +23,6 @@ fn sanitize_extremes() {
last_connection_state: Default::default(),
};
check::sanitize_avoids_leaks(&min_time_entry);
check::sanitize_avoids_leaks(&max_time_entry);
check::sanitize_avoids_leaks(&min_time_entry, &min_time_entry.sanitize());
check::sanitize_avoids_leaks(&max_time_entry, &max_time_entry.sanitize());
}