diff --git a/zebra-network/src/meta_addr.rs b/zebra-network/src/meta_addr.rs index ff0aaae1f..861b578e7 100644 --- a/zebra-network/src/meta_addr.rs +++ b/zebra-network/src/meta_addr.rs @@ -3,22 +3,13 @@ use std::{ cmp::{Ord, Ordering}, convert::TryInto, - io::{Read, Write}, net::SocketAddr, time::Instant, }; -use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use zebra_chain::serialization::{canonical_socket_addr, DateTime32}; -use zebra_chain::serialization::{ - canonical_socket_addr, DateTime32, ReadZcashExt, SerializationError, TrustedPreallocate, - WriteZcashExt, ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize, -}; - -use crate::{ - constants, - protocol::{external::MAX_PROTOCOL_MESSAGE_LEN, types::PeerServices}, -}; +use crate::{constants, protocol::types::PeerServices}; use MetaAddrChange::*; use PeerAddrState::*; @@ -910,53 +901,3 @@ impl PartialEq for MetaAddr { } impl Eq for MetaAddr {} - -impl ZcashSerialize for MetaAddr { - fn zcash_serialize(&self, mut writer: W) -> Result<(), std::io::Error> { - self.last_seen() - .expect( - "unexpected MetaAddr with missing last seen time: MetaAddrs should be sanitized \ - before serialization", - ) - .zcash_serialize(&mut writer)?; - - writer.write_u64::( - self.services - .expect( - "unexpected MetaAddr with missing peer services: MetaAddrs should be \ - sanitized before serialization", - ) - .bits(), - )?; - - writer.write_socket_addr(self.addr)?; - - Ok(()) - } -} - -impl ZcashDeserialize for MetaAddr { - fn zcash_deserialize(mut reader: R) -> Result { - let untrusted_last_seen = (&mut reader).zcash_deserialize_into()?; - let untrusted_services = - PeerServices::from_bits_truncate(reader.read_u64::()?); - let addr = reader.read_socket_addr()?; - - Ok(MetaAddr::new_gossiped_meta_addr( - addr, - untrusted_services, - untrusted_last_seen, - )) - } -} - -/// A serialized meta addr has a 4 byte time, 8 byte services, 16 byte IP addr, and 2 byte port -const META_ADDR_SIZE: usize = 4 + 8 + 16 + 2; - -impl TrustedPreallocate for MetaAddr { - fn max_allocation() -> u64 { - // Since a maximal serialized Vec uses at least three bytes for its length (2MB messages / 30B MetaAddr implies the maximal length is much greater than 253) - // the max allocation can never exceed (MAX_PROTOCOL_MESSAGE_LEN - 3) / META_ADDR_SIZE - ((MAX_PROTOCOL_MESSAGE_LEN - 3) / META_ADDR_SIZE) as u64 - } -} diff --git a/zebra-network/src/meta_addr/tests.rs b/zebra-network/src/meta_addr/tests.rs index 76e880cbe..f4277e20a 100644 --- a/zebra-network/src/meta_addr/tests.rs +++ b/zebra-network/src/meta_addr/tests.rs @@ -1,4 +1,3 @@ mod check; -mod preallocate; mod prop; mod vectors; diff --git a/zebra-network/src/meta_addr/tests/preallocate.rs b/zebra-network/src/meta_addr/tests/preallocate.rs deleted file mode 100644 index e1c9560c7..000000000 --- a/zebra-network/src/meta_addr/tests/preallocate.rs +++ /dev/null @@ -1,65 +0,0 @@ -//! Tests for trusted preallocation during deserialization. - -use super::super::{MetaAddr, META_ADDR_SIZE}; - -use zebra_chain::serialization::{TrustedPreallocate, ZcashSerialize, MAX_PROTOCOL_MESSAGE_LEN}; - -use proptest::prelude::*; -use std::convert::TryInto; - -proptest! { - /// Confirm that each MetaAddr takes exactly META_ADDR_SIZE bytes when serialized. - /// This verifies that our calculated `TrustedPreallocate::max_allocation()` is indeed an upper bound. - #[test] - fn meta_addr_size_is_correct(addr in MetaAddr::arbitrary()) { - zebra_test::init(); - - // We require sanitization before serialization - let addr = addr.sanitize(); - prop_assume!(addr.is_some()); - let addr = addr.unwrap(); - - let serialized = addr - .zcash_serialize_to_vec() - .expect("Serialization to vec must succeed"); - assert!(serialized.len() == META_ADDR_SIZE) - } - - /// Verifies that... - /// 1. The smallest disallowed vector of `MetaAddrs`s is too large to fit in a legal Zcash message - /// 2. The largest allowed vector is small enough to fit in a legal Zcash message - #[test] - fn meta_addr_max_allocation_is_correct(addr in MetaAddr::arbitrary()) { - zebra_test::init(); - - // We require sanitization before serialization - let addr = addr.sanitize(); - prop_assume!(addr.is_some()); - let addr = addr.unwrap(); - - let max_allocation: usize = MetaAddr::max_allocation().try_into().unwrap(); - let mut smallest_disallowed_vec = Vec::with_capacity(max_allocation + 1); - for _ in 0..(MetaAddr::max_allocation() + 1) { - smallest_disallowed_vec.push(addr); - } - let smallest_disallowed_serialized = smallest_disallowed_vec - .zcash_serialize_to_vec() - .expect("Serialization to vec must succeed"); - // Check that our smallest_disallowed_vec is only one item larger than the limit - assert!(((smallest_disallowed_vec.len() - 1) as u64) == MetaAddr::max_allocation()); - // Check that our smallest_disallowed_vec is too big to send in a valid Zcash message - assert!(smallest_disallowed_serialized.len() > MAX_PROTOCOL_MESSAGE_LEN); - - // Create largest_allowed_vec by removing one element from smallest_disallowed_vec without copying (for efficiency) - smallest_disallowed_vec.pop(); - let largest_allowed_vec = smallest_disallowed_vec; - let largest_allowed_serialized = largest_allowed_vec - .zcash_serialize_to_vec() - .expect("Serialization to vec must succeed"); - - // Check that our largest_allowed_vec contains the maximum number of MetaAddrs - assert!((largest_allowed_vec.len() as u64) == MetaAddr::max_allocation()); - // Check that our largest_allowed_vec is small enough to fit in a Zcash message. - assert!(largest_allowed_serialized.len() <= MAX_PROTOCOL_MESSAGE_LEN); - } -} diff --git a/zebra-network/src/protocol/external.rs b/zebra-network/src/protocol/external.rs index 8698f488b..5c10d844e 100644 --- a/zebra-network/src/protocol/external.rs +++ b/zebra-network/src/protocol/external.rs @@ -1,3 +1,7 @@ +//! Network protocol types and serialization for the Zcash wire format. + +/// Node address wire formats. +mod addr; /// A Tokio codec that transforms an `AsyncRead` into a `Stream` of `Message`s. pub mod codec; /// Inventory items. diff --git a/zebra-network/src/protocol/external/addr.rs b/zebra-network/src/protocol/external/addr.rs new file mode 100644 index 000000000..07a58fef4 --- /dev/null +++ b/zebra-network/src/protocol/external/addr.rs @@ -0,0 +1,65 @@ +//! Node address types and serialization for the Zcash wire format. + +use std::io::{Read, Write}; + +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; + +use zebra_chain::serialization::{ + ReadZcashExt, SerializationError, TrustedPreallocate, WriteZcashExt, ZcashDeserialize, + ZcashDeserializeInto, ZcashSerialize, +}; + +use crate::{ + meta_addr::MetaAddr, + protocol::external::{types::PeerServices, MAX_PROTOCOL_MESSAGE_LEN}, +}; + +impl ZcashSerialize for MetaAddr { + fn zcash_serialize(&self, mut writer: W) -> Result<(), std::io::Error> { + self.last_seen() + .expect( + "unexpected MetaAddr with missing last seen time: MetaAddrs should be sanitized \ + before serialization", + ) + .zcash_serialize(&mut writer)?; + + writer.write_u64::( + self.services + .expect( + "unexpected MetaAddr with missing peer services: MetaAddrs should be \ + sanitized before serialization", + ) + .bits(), + )?; + + writer.write_socket_addr(self.addr)?; + + Ok(()) + } +} + +impl ZcashDeserialize for MetaAddr { + fn zcash_deserialize(mut reader: R) -> Result { + let untrusted_last_seen = (&mut reader).zcash_deserialize_into()?; + let untrusted_services = + PeerServices::from_bits_truncate(reader.read_u64::()?); + let addr = reader.read_socket_addr()?; + + Ok(MetaAddr::new_gossiped_meta_addr( + addr, + untrusted_services, + untrusted_last_seen, + )) + } +} + +/// A serialized meta addr has a 4 byte time, 8 byte services, 16 byte IP addr, and 2 byte port +pub(super) const META_ADDR_SIZE: usize = 4 + 8 + 16 + 2; + +impl TrustedPreallocate for MetaAddr { + fn max_allocation() -> u64 { + // Since a maximal serialized Vec uses at least three bytes for its length (2MB messages / 30B MetaAddr implies the maximal length is much greater than 253) + // the max allocation can never exceed (MAX_PROTOCOL_MESSAGE_LEN - 3) / META_ADDR_SIZE + ((MAX_PROTOCOL_MESSAGE_LEN - 3) / META_ADDR_SIZE) as u64 + } +} diff --git a/zebra-network/src/protocol/external/codec.rs b/zebra-network/src/protocol/external/codec.rs index 6a859aaa7..ee794dbbc 100644 --- a/zebra-network/src/protocol/external/codec.rs +++ b/zebra-network/src/protocol/external/codec.rs @@ -1,8 +1,8 @@ //! A Tokio codec mapping byte streams to Bitcoin message streams. -use std::fmt; use std::{ cmp::min, + fmt, io::{Cursor, Read, Write}, }; diff --git a/zebra-network/src/protocol/external/tests/preallocate.rs b/zebra-network/src/protocol/external/tests/preallocate.rs index 77709364b..8dab828b1 100644 --- a/zebra-network/src/protocol/external/tests/preallocate.rs +++ b/zebra-network/src/protocol/external/tests/preallocate.rs @@ -1,11 +1,14 @@ //! Tests for trusted preallocation during deserialization. -use super::super::inv::InventoryHash; +use std::convert::TryInto; + +use proptest::prelude::*; use zebra_chain::serialization::{TrustedPreallocate, ZcashSerialize, MAX_PROTOCOL_MESSAGE_LEN}; -use proptest::prelude::*; -use std::convert::TryInto; +use crate::meta_addr::MetaAddr; + +use super::super::{addr::META_ADDR_SIZE, inv::InventoryHash}; proptest! { /// Confirm that each InventoryHash takes the expected size in bytes when serialized. @@ -58,3 +61,60 @@ proptest! { assert!(largest_allowed_serialized.len() <= MAX_PROTOCOL_MESSAGE_LEN); } } + +proptest! { + /// Confirm that each MetaAddr takes exactly META_ADDR_SIZE bytes when serialized. + /// This verifies that our calculated `TrustedPreallocate::max_allocation()` is indeed an upper bound. + #[test] + fn meta_addr_size_is_correct(addr in MetaAddr::arbitrary()) { + zebra_test::init(); + + // We require sanitization before serialization + let addr = addr.sanitize(); + prop_assume!(addr.is_some()); + let addr = addr.unwrap(); + + let serialized = addr + .zcash_serialize_to_vec() + .expect("Serialization to vec must succeed"); + assert!(serialized.len() == META_ADDR_SIZE) + } + + /// Verifies that... + /// 1. The smallest disallowed vector of `MetaAddrs`s is too large to fit in a legal Zcash message + /// 2. The largest allowed vector is small enough to fit in a legal Zcash message + #[test] + fn meta_addr_max_allocation_is_correct(addr in MetaAddr::arbitrary()) { + zebra_test::init(); + + // We require sanitization before serialization + let addr = addr.sanitize(); + prop_assume!(addr.is_some()); + let addr = addr.unwrap(); + + let max_allocation: usize = MetaAddr::max_allocation().try_into().unwrap(); + let mut smallest_disallowed_vec = Vec::with_capacity(max_allocation + 1); + for _ in 0..(MetaAddr::max_allocation() + 1) { + smallest_disallowed_vec.push(addr); + } + let smallest_disallowed_serialized = smallest_disallowed_vec + .zcash_serialize_to_vec() + .expect("Serialization to vec must succeed"); + // Check that our smallest_disallowed_vec is only one item larger than the limit + assert!(((smallest_disallowed_vec.len() - 1) as u64) == MetaAddr::max_allocation()); + // Check that our smallest_disallowed_vec is too big to send in a valid Zcash message + assert!(smallest_disallowed_serialized.len() > MAX_PROTOCOL_MESSAGE_LEN); + + // Create largest_allowed_vec by removing one element from smallest_disallowed_vec without copying (for efficiency) + smallest_disallowed_vec.pop(); + let largest_allowed_vec = smallest_disallowed_vec; + let largest_allowed_serialized = largest_allowed_vec + .zcash_serialize_to_vec() + .expect("Serialization to vec must succeed"); + + // Check that our largest_allowed_vec contains the maximum number of MetaAddrs + assert!((largest_allowed_vec.len() as u64) == MetaAddr::max_allocation()); + // Check that our largest_allowed_vec is small enough to fit in a Zcash message. + assert!(largest_allowed_serialized.len() <= MAX_PROTOCOL_MESSAGE_LEN); + } +}