diff --git a/zebra-chain/src/transaction/tests/preallocate.rs b/zebra-chain/src/transaction/tests/preallocate.rs index 220484b2..5dfde4f8 100644 --- a/zebra-chain/src/transaction/tests/preallocate.rs +++ b/zebra-chain/src/transaction/tests/preallocate.rs @@ -63,7 +63,7 @@ proptest! { // Check that our largest_allowed_vec contains the maximum number of Transactions prop_assert!((largest_allowed_vec_len as u64) == Transaction::max_allocation()); - // largest_allowed_serialized_len exceeds the limit for variable-sized types + // This is a variable-sized type, so largest_allowed_serialized_len can exceed the length limit } /// Verify the smallest disallowed vector of `Input`s is too large to fit in a Zcash block @@ -86,7 +86,7 @@ proptest! { // Check that our largest_allowed_vec contains the maximum number of Inputs prop_assert!((largest_allowed_vec_len as u64) == Input::max_allocation()); - // largest_allowed_serialized_len exceeds the limit for variable-sized types + // This is a variable-sized type, so largest_allowed_serialized_len can exceed the length limit } /// Verify the smallest disallowed vector of `Output`s is too large to fit in a Zcash block @@ -109,6 +109,6 @@ proptest! { // Check that our largest_allowed_vec contains the maximum number of Outputs prop_assert!((largest_allowed_vec_len as u64) == Output::max_allocation()); - // largest_allowed_serialized_len exceeds the limit for variable-sized types + // This is a variable-sized type, so largest_allowed_serialized_len can exceed the length limit } } diff --git a/zebra-network/src/constants.rs b/zebra-network/src/constants.rs index 9753073d..6870e52d 100644 --- a/zebra-network/src/constants.rs +++ b/zebra-network/src/constants.rs @@ -121,6 +121,20 @@ pub const MIN_PEER_GET_ADDR_INTERVAL: Duration = Duration::from_secs(10); /// response (#1869) pub const GET_ADDR_FANOUT: usize = 3; +/// The maximum number of addresses allowed in an `addr` or `addrv2` message. +/// +/// `addr`: +/// > The number of IP address entries up to a maximum of 1,000. +/// +/// https://developer.bitcoin.org/reference/p2p_networking.html#addr +/// +/// `addrv2`: +/// > One message can contain up to 1,000 addresses. +/// > Clients MUST reject messages with more addresses. +/// +/// https://zips.z.cash/zip-0155#specification +pub const MAX_ADDRS_IN_MESSAGE: usize = 1000; + /// Truncate timestamps in outbound address messages to this time interval. /// /// ## SECURITY diff --git a/zebra-network/src/protocol/external/addr.rs b/zebra-network/src/protocol/external/addr.rs index a9f8f3af..cc070a63 100644 --- a/zebra-network/src/protocol/external/addr.rs +++ b/zebra-network/src/protocol/external/addr.rs @@ -9,6 +9,7 @@ pub mod canonical; pub mod in_version; pub(crate) mod v1; +pub(crate) mod v2; pub use canonical::canonical_socket_addr; pub use in_version::AddrInVersion; @@ -17,6 +18,12 @@ pub use in_version::AddrInVersion; // so that they don't leak outside the serialization code. pub(super) use v1::AddrV1; +pub(super) use v2::AddrV2; #[cfg(any(test, feature = "proptest-impl"))] pub(super) use v1::{ipv6_mapped_socket_addr, ADDR_V1_SIZE}; + +// TODO: write tests for addrv2 deserialization +#[allow(unused_imports)] +#[cfg(any(test, feature = "proptest-impl"))] +pub(super) use v2::ADDR_V2_MIN_SIZE; diff --git a/zebra-network/src/protocol/external/addr/in_version.rs b/zebra-network/src/protocol/external/addr/in_version.rs index 91e789ea..e2e2d7b1 100644 --- a/zebra-network/src/protocol/external/addr/in_version.rs +++ b/zebra-network/src/protocol/external/addr/in_version.rs @@ -24,8 +24,8 @@ use crate::protocol::external::arbitrary::addr_v1_ipv6_mapped_socket_addr_strate use super::{canonical_socket_addr, v1::ipv6_mapped_socket_addr}; -/// A version 1 node address and services, without a last-seen time. -/// This struct is serialized and deserialized as part of `version` messages. +/// The format used for Bitcoin node addresses in `version` messages. +/// Contains a node address and services, without a last-seen time. /// /// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#Network_address) #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] diff --git a/zebra-network/src/protocol/external/addr/v1.rs b/zebra-network/src/protocol/external/addr/v1.rs index 14dcce41..8b09f90c 100644 --- a/zebra-network/src/protocol/external/addr/v1.rs +++ b/zebra-network/src/protocol/external/addr/v1.rs @@ -28,8 +28,9 @@ use proptest_derive::Arbitrary; #[cfg(any(test, feature = "proptest-impl"))] use crate::protocol::external::arbitrary::addr_v1_ipv6_mapped_socket_addr_strategy; -/// A version 1 node address, its advertised services, and last-seen time. -/// This struct is serialized and deserialized into `addr` messages. +/// The first format used for Bitcoin node addresses. +/// Contains a node address, its advertised services, and last-seen time. +/// This struct is serialized and deserialized into `addr` (v1) messages. /// /// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#Network_address) #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] @@ -85,11 +86,11 @@ impl From for AddrV1 { } impl From for MetaAddr { - fn from(addr_v1: AddrV1) -> Self { + fn from(addr: AddrV1) -> Self { MetaAddr::new_gossiped_meta_addr( - addr_v1.ipv6_addr.into(), - addr_v1.untrusted_services, - addr_v1.untrusted_last_seen, + addr.ipv6_addr.into(), + addr.untrusted_services, + addr.untrusted_last_seen, ) } } @@ -131,10 +132,8 @@ pub(in super::super) const ADDR_V1_SIZE: usize = 4 + 8 + 16 + 2; impl TrustedPreallocate for AddrV1 { fn max_allocation() -> u64 { - // Since a maximal serialized Vec uses at least three bytes for its length - // (2MB messages / 30B AddrV1 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) / ADDR_V1_SIZE) as u64 + // Since ADDR_V1_SIZE is less than 2^5, the length of the largest list takes up 5 bytes. + ((MAX_PROTOCOL_MESSAGE_LEN - 5) / ADDR_V1_SIZE) as u64 } } diff --git a/zebra-network/src/protocol/external/addr/v2.rs b/zebra-network/src/protocol/external/addr/v2.rs new file mode 100644 index 00000000..009d3b59 --- /dev/null +++ b/zebra-network/src/protocol/external/addr/v2.rs @@ -0,0 +1,325 @@ +//! Zcash `addrv2` message node address serialization. +//! +//! Zebra parses received IPv4 and IPv6 addresses in the [`AddrV2`] format. +//! But it ignores all other address types. +//! +//! Zebra never sends `addrv2` messages, because peers still accept `addr` (v1) messages. + +use std::{ + convert::{TryFrom, TryInto}, + io::Read, + net::{IpAddr, SocketAddr}, +}; + +use byteorder::{BigEndian, ReadBytesExt}; +use thiserror::Error; + +use zebra_chain::serialization::{ + CompactSize64, DateTime32, SerializationError, TrustedPreallocate, ZcashDeserialize, + ZcashDeserializeInto, +}; + +use crate::{ + meta_addr::MetaAddr, + protocol::external::{types::PeerServices, MAX_PROTOCOL_MESSAGE_LEN}, +}; + +#[cfg(any(test, feature = "proptest-impl"))] +use proptest_derive::Arbitrary; + +#[cfg(test)] +use byteorder::WriteBytesExt; +#[cfg(test)] +use std::io::Write; +#[cfg(test)] +use zebra_chain::serialization::{zcash_serialize_bytes, ZcashSerialize}; + +/// The maximum permitted size of the `addr` field in `addrv2` messages. +/// +/// > Field addr has a variable length, with a maximum of 512 bytes (4096 bits). +/// > Clients MUST reject messages with a longer addr field, irrespective of the network ID. +/// +/// https://zips.z.cash/zip-0155#specification +pub const MAX_ADDR_V2_ADDR_SIZE: usize = 512; + +/// The network ID of [`Ipv4Addr`]s in `addrv2` messages. +/// +/// > 0x01 IPV4 4 IPv4 address (globally routed internet) +/// +/// https://zips.z.cash/zip-0155#specification +pub const ADDR_V2_IPV4_NETWORK_ID: u8 = 0x01; + +/// The size of [`Ipv4Addr`]s in `addrv2` messages. +/// +/// https://zips.z.cash/zip-0155#specification +pub const ADDR_V2_IPV4_ADDR_SIZE: usize = 4; + +/// The network ID of [`Ipv6Addr`]s in `addrv2` messages. +/// +/// > 0x02 IPV6 16 IPv6 address (globally routed internet) +/// +/// https://zips.z.cash/zip-0155#specification +pub const ADDR_V2_IPV6_NETWORK_ID: u8 = 0x02; + +/// The size of [`Ipv6Addr`]s in `addrv2` messages. +/// +/// https://zips.z.cash/zip-0155#specification +pub const ADDR_V2_IPV6_ADDR_SIZE: usize = 16; + +/// The second format used for Bitcoin node addresses. +/// Contains a node address, its advertised services, and last-seen time. +/// This struct is serialized and deserialized into `addrv2` messages. +/// +/// [ZIP 155](https://zips.z.cash/zip-0155#specification) +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] +pub(in super::super) enum AddrV2 { + /// An IPv4 or IPv6 node address, in `addrv2` format. + IpAddr { + /// The unverified "last seen time" gossiped by the remote peer that sent us + /// this address. + /// + /// See the [`MetaAddr::last_seen`] method for details. + untrusted_last_seen: DateTime32, + + /// The unverified services for the peer at `ip_addr`:`port`. + /// + /// These services were advertised by the peer at that address, + /// then gossiped via another peer. + /// + /// ## Security + /// + /// `untrusted_services` on gossiped peers may be invalid due to outdated + /// records, older peer versions, or buggy or malicious peers. + untrusted_services: PeerServices, + + /// The peer's IP address. + /// + /// Unlike [`AddrV1`], this can be an IPv4 or IPv6 address. + ip: IpAddr, + + /// The peer's TCP port. + port: u16, + }, + + /// A node address with an unsupported `networkID`, in `addrv2` format. + Unsupported, +} + +// Just serialize in the tests for now. +// +// We can't guarantee that peers support addrv2 until it activates, +// and outdated peers are excluded from the network by a network upgrade. +// (Likely NU5 on mainnet, and NU6 on testnet.) +// https://zips.z.cash/zip-0155#deployment +// +// And Zebra doesn't use different codecs for different peer versions. +#[cfg(test)] +impl From for AddrV2 { + fn from(meta_addr: MetaAddr) -> Self { + let untrusted_services = meta_addr.services.expect( + "unexpected MetaAddr with missing peer services: \ + MetaAddrs should be sanitized before serialization", + ); + let untrusted_last_seen = meta_addr.last_seen().expect( + "unexpected MetaAddr with missing last seen time: \ + MetaAddrs should be sanitized before serialization", + ); + + AddrV2::IpAddr { + untrusted_last_seen, + untrusted_services, + ip: meta_addr.addr.ip(), + port: meta_addr.addr.port(), + } + } +} + +/// The error returned when converting `AddrV2::Unsupported` fails. +#[derive(Error, Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] +#[error("can not parse this addrv2 variant: unimplemented or unrecognised AddrV2 network ID")] +pub struct UnsupportedAddrV2NetworkIdError; + +impl TryFrom for MetaAddr { + type Error = UnsupportedAddrV2NetworkIdError; + + fn try_from(addr: AddrV2) -> Result { + if let AddrV2::IpAddr { + untrusted_last_seen, + untrusted_services, + ip, + port, + } = addr + { + let addr = SocketAddr::new(ip, port); + + Ok(MetaAddr::new_gossiped_meta_addr( + addr, + untrusted_services, + untrusted_last_seen, + )) + } else { + Err(UnsupportedAddrV2NetworkIdError) + } + } +} + +impl AddrV2 { + /// Deserialize `addr_bytes` as an IPv4 or IPv6 address, using the `addrv2` format. + /// Returns the corresponding [`IpAddr`]. + /// + /// The returned IP version is chosen based on `IP_ADDR_SIZE`, + /// which should be [`ADDR_V2_IPV4_ADDR_SIZE`] or [`ADDR_V2_IPV6_ADDR_SIZE`]. + fn ip_addr_from_bytes( + addr_bytes: Vec, + ) -> Result + where + IpAddr: From<[u8; IP_ADDR_SIZE]>, + { + // > Clients MUST reject messages that contain addresses that have + // > a different length than specified in this table for a specific network ID, + // > as these are meaningless. + if addr_bytes.len() != IP_ADDR_SIZE { + let error_msg = if IP_ADDR_SIZE == ADDR_V2_IPV4_ADDR_SIZE { + "IP address field length did not match expected IPv4 address size in addrv2 message" + } else if IP_ADDR_SIZE == ADDR_V2_IPV6_ADDR_SIZE { + "IP address field length did not match expected IPv6 address size in addrv2 message" + } else { + unreachable!("unexpected IP address size when converting from bytes"); + }; + + return Err(SerializationError::Parse(error_msg)); + }; + + // > The IPV4 and IPV6 network IDs use addresses encoded in the usual way + // > for binary IPv4 and IPv6 addresses in network byte order (big endian). + let ip: [u8; IP_ADDR_SIZE] = addr_bytes.try_into().expect("just checked length"); + + Ok(IpAddr::from(ip)) + } +} + +// Just serialize in the tests for now. +// +// See the detailed note about ZIP-155 activation above. +#[cfg(test)] +impl ZcashSerialize for AddrV2 { + fn zcash_serialize(&self, mut writer: W) -> Result<(), std::io::Error> { + if let AddrV2::IpAddr { + untrusted_last_seen, + untrusted_services, + ip, + port, + } = self + { + // > uint32 Time that this node was last seen as connected to the network. + untrusted_last_seen.zcash_serialize(&mut writer)?; + + // > Service bits. A CompactSize-encoded bit field that is 64 bits wide. + let untrusted_services: CompactSize64 = untrusted_services.bits().into(); + untrusted_services.zcash_serialize(&mut writer)?; + + match ip { + IpAddr::V4(ip) => { + // > Network identifier. An 8-bit value that specifies which network is addressed. + writer.write_u8(ADDR_V2_IPV4_NETWORK_ID)?; + + // > The IPV4 and IPV6 network IDs use addresses encoded in the usual way + // > for binary IPv4 and IPv6 addresses in network byte order (big endian). + let ip: [u8; ADDR_V2_IPV4_ADDR_SIZE] = ip.octets(); + // > CompactSize The length in bytes of addr. + // > uint8[sizeAddr] Network address. The interpretation depends on networkID. + zcash_serialize_bytes(&ip.to_vec(), &mut writer)?; + + // > uint16 Network port. If not relevant for the network this MUST be 0. + writer.write_u16::(*port)?; + } + IpAddr::V6(ip) => { + writer.write_u8(ADDR_V2_IPV6_NETWORK_ID)?; + + let ip: [u8; ADDR_V2_IPV6_ADDR_SIZE] = ip.octets(); + zcash_serialize_bytes(&ip.to_vec(), &mut writer)?; + + writer.write_u16::(*port)?; + } + } + } else { + unreachable!("unexpected AddrV2 variant: {:?}", self); + } + + Ok(()) + } +} + +/// Deserialize an `addrv2` entry according to: +/// https://zips.z.cash/zip-0155#specification +/// +/// Unimplemented and unrecognised addresses are deserialized as [`AddrV2::Unsupported`]. +/// (Deserialization consumes the correct number of bytes for unsupported addresses.) +impl ZcashDeserialize for AddrV2 { + fn zcash_deserialize(mut reader: R) -> Result { + // > uint32 Time that this node was last seen as connected to the network. + let untrusted_last_seen = (&mut reader).zcash_deserialize_into()?; + + // > Service bits. A CompactSize-encoded bit field that is 64 bits wide. + let untrusted_services: CompactSize64 = (&mut reader).zcash_deserialize_into()?; + let untrusted_services = PeerServices::from_bits_truncate(untrusted_services.into()); + + // > Network identifier. An 8-bit value that specifies which network is addressed. + // + // See the list of reserved network IDs in ZIP 155. + let network_id = reader.read_u8()?; + + // > CompactSize The length in bytes of addr. + // > uint8[sizeAddr] Network address. The interpretation depends on networkID. + let addr: Vec = (&mut reader).zcash_deserialize_into()?; + + // > uint16 Network port. If not relevant for the network this MUST be 0. + let port = reader.read_u16::()?; + + if addr.len() > MAX_ADDR_V2_ADDR_SIZE { + return Err(SerializationError::Parse( + "addr field longer than MAX_ADDR_V2_ADDR_SIZE in addrv2 message", + )); + } + + let ip = if network_id == ADDR_V2_IPV4_NETWORK_ID { + AddrV2::ip_addr_from_bytes::(addr)? + } else if network_id == ADDR_V2_IPV6_NETWORK_ID { + AddrV2::ip_addr_from_bytes::(addr)? + } else { + // unimplemented or unrecognised network ID, just consume the bytes + // + // > Clients MUST NOT gossip addresses from unknown networks, + // > because they have no means to validate those addresses + // > and so can be tricked to gossip invalid addresses. + + return Ok(AddrV2::Unsupported); + }; + + Ok(AddrV2::IpAddr { + untrusted_last_seen, + untrusted_services, + ip, + port, + }) + } +} + +/// A serialized `addrv2` has: +/// * 4 byte time, +/// * 1-9 byte services, +/// * 1 byte networkID, +/// * 1-9 byte sizeAddr, +/// * 0-512 bytes addr, +/// * 2 bytes port. +#[allow(clippy::identity_op)] +pub(in super::super) const ADDR_V2_MIN_SIZE: usize = 4 + 1 + 1 + 1 + 0 + 2; + +impl TrustedPreallocate for AddrV2 { + fn max_allocation() -> u64 { + // Since ADDR_V2_MIN_SIZE is less than 2^5, the length of the largest list takes up 5 bytes. + ((MAX_PROTOCOL_MESSAGE_LEN - 5) / ADDR_V2_MIN_SIZE) as u64 + } +} diff --git a/zebra-network/src/protocol/external/codec.rs b/zebra-network/src/protocol/external/codec.rs index 1a7bda9f..94354464 100644 --- a/zebra-network/src/protocol/external/codec.rs +++ b/zebra-network/src/protocol/external/codec.rs @@ -2,6 +2,7 @@ use std::{ cmp::min, + convert::TryInto, fmt, io::{Cursor, Read, Write}, }; @@ -25,7 +26,7 @@ use zebra_chain::{ use crate::constants; use super::{ - addr::{AddrInVersion, AddrV1}, + addr::{AddrInVersion, AddrV1, AddrV2}, message::{Message, RejectReason}, types::*, }; @@ -242,6 +243,11 @@ impl Codec { } } Message::Addr(addrs) => { + assert!( + addrs.len() <= constants::MAX_ADDRS_IN_MESSAGE, + "unexpectely large Addr message: greater than MAX_ADDRS_IN_MESSAGE addresses" + ); + // Regardless of the way we received the address, // Zebra always sends `addr` messages let v1_addrs: Vec = addrs.iter().map(|addr| AddrV1::from(*addr)).collect(); @@ -408,6 +414,7 @@ impl Decoder for Codec { b"pong\0\0\0\0\0\0\0\0" => self.read_pong(&mut body_reader), b"reject\0\0\0\0\0\0" => self.read_reject(&mut body_reader), b"addr\0\0\0\0\0\0\0\0" => self.read_addr(&mut body_reader), + b"addrv2\0\0\0\0\0\0" => self.read_addrv2(&mut body_reader), b"getaddr\0\0\0\0\0" => self.read_getaddr(&mut body_reader), b"block\0\0\0\0\0\0\0" => self.read_block(&mut body_reader), b"getblocks\0\0\0" => self.read_getblocks(&mut body_reader), @@ -510,14 +517,42 @@ impl Codec { } /// Deserialize an `addr` (v1) message into a list of `MetaAddr`s. - fn read_addr(&self, reader: R) -> Result { + pub(super) fn read_addr(&self, reader: R) -> Result { let addrs: Vec = reader.zcash_deserialize_into()?; + if addrs.len() > constants::MAX_ADDRS_IN_MESSAGE { + return Err(Error::Parse( + "more than MAX_ADDRS_IN_MESSAGE in addr message", + )); + } + // Convert the received address format to Zebra's internal `MetaAddr`. let addrs = addrs.into_iter().map(Into::into).collect(); Ok(Message::Addr(addrs)) } + /// Deserialize an `addrv2` message into a list of `MetaAddr`s. + /// + /// Currently, Zebra parses received `addrv2`s, ignoring some address types. + /// Zebra never sends `addrv2` messages. + pub(super) fn read_addrv2(&self, reader: R) -> Result { + let addrs: Vec = reader.zcash_deserialize_into()?; + + if addrs.len() > constants::MAX_ADDRS_IN_MESSAGE { + return Err(Error::Parse( + "more than MAX_ADDRS_IN_MESSAGE in addrv2 message", + )); + } + + // Convert the received address format to Zebra's internal `MetaAddr`, + // ignoring unsupported network IDs. + let addrs = addrs + .into_iter() + .filter_map(|addr| addr.try_into().ok()) + .collect(); + Ok(Message::Addr(addrs)) + } + fn read_getaddr(&self, mut _reader: R) -> Result { Ok(Message::GetAddr) } diff --git a/zebra-network/src/protocol/external/message.rs b/zebra-network/src/protocol/external/message.rs index 0c6deed2..f1423569 100644 --- a/zebra-network/src/protocol/external/message.rs +++ b/zebra-network/src/protocol/external/message.rs @@ -142,9 +142,12 @@ pub enum Message { /// /// Currently, Zebra: /// - sends and receives `addr` messages, - /// - parses received `addrv2` messages, + /// - parses received `addrv2` messages, ignoring some address types, /// - but does not send `addrv2` messages. /// + /// Because some address types are ignored, the deserialized vector can be empty, + /// even if the peer sent addresses. This is not an error. + /// /// [addr Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#addr) /// [addrv2 ZIP 155](https://zips.z.cash/zip-0155#specification) Addr(Vec), diff --git a/zebra-network/src/protocol/external/tests/preallocate.rs b/zebra-network/src/protocol/external/tests/preallocate.rs index f16fe4fc..d895cca3 100644 --- a/zebra-network/src/protocol/external/tests/preallocate.rs +++ b/zebra-network/src/protocol/external/tests/preallocate.rs @@ -12,7 +12,7 @@ use zebra_chain::serialization::{ use crate::{ meta_addr::MetaAddr, protocol::external::{ - addr::{AddrV1, ADDR_V1_SIZE}, + addr::{AddrV1, AddrV2, ADDR_V1_SIZE, ADDR_V2_MIN_SIZE}, inv::InventoryHash, }, }; @@ -117,3 +117,53 @@ proptest! { prop_assert!(largest_allowed_serialized_len <= MAX_PROTOCOL_MESSAGE_LEN); } } + +proptest! { + /// Confirm that each AddrV2 takes at least ADDR_V2_MIN_SIZE bytes when serialized. + /// This verifies that our calculated `TrustedPreallocate::max_allocation()` is indeed an upper bound. + #[test] + fn addr_v2_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: AddrV2 = addr.unwrap().into(); + + let serialized = addr + .zcash_serialize_to_vec() + .expect("Serialization to vec must succeed"); + prop_assert!(serialized.len() >= ADDR_V2_MIN_SIZE) + } + + /// Verifies that... + /// 1. The smallest disallowed vector of `AddrV2`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 addr_v2_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: AddrV2 = addr.unwrap().into(); + + let ( + smallest_disallowed_vec_len, + smallest_disallowed_serialized_len, + largest_allowed_vec_len, + _largest_allowed_serialized_len, + ) = max_allocation_is_big_enough(addr); + + // Check that our smallest_disallowed_vec is only one item larger than the limit + prop_assert!(((smallest_disallowed_vec_len - 1) as u64) == AddrV2::max_allocation()); + // Check that our smallest_disallowed_vec is too big to send in a valid Zcash message + prop_assert!(smallest_disallowed_serialized_len > MAX_PROTOCOL_MESSAGE_LEN); + + // Check that our largest_allowed_vec contains the maximum number of AddrV2s + prop_assert!((largest_allowed_vec_len as u64) == AddrV2::max_allocation()); + // This is a variable-sized type, so largest_allowed_serialized_len can exceed the length limit + } +} diff --git a/zebra-network/src/protocol/external/tests/prop.rs b/zebra-network/src/protocol/external/tests/prop.rs index d64845c0..5518e4e5 100644 --- a/zebra-network/src/protocol/external/tests/prop.rs +++ b/zebra-network/src/protocol/external/tests/prop.rs @@ -1,5 +1,7 @@ //! Randomised property tests for Zebra's Zcash network protocol types. +use std::convert::TryInto; + use bytes::BytesMut; use proptest::{collection::vec, prelude::*}; use tokio_util::codec::{Decoder, Encoder}; @@ -9,9 +11,13 @@ use zebra_chain::serialization::{ MAX_PROTOCOL_MESSAGE_LEN, }; -use crate::{meta_addr::tests::check, meta_addr::MetaAddr, protocol::external::addr::AddrV1}; - -use super::super::{Codec, InventoryHash, Message}; +use crate::{ + meta_addr::{tests::check, MetaAddr}, + protocol::external::{ + addr::{AddrV1, AddrV2}, + Codec, InventoryHash, Message, + }, +}; /// Maximum number of random input bytes to try to deserialize an [`InventoryHash`] from. /// @@ -95,72 +101,9 @@ proptest! { prop_assert_eq!(decoded.unwrap(), Some(message)); } - /// Test round-trip AddrV1 serialization for gossiped MetaAddrs - #[test] - fn gossiped_roundtrip(gossiped_addr in MetaAddr::gossiped_strategy()) { - zebra_test::init(); - - // We require sanitization before serialization - let gossiped_addr = gossiped_addr.sanitize(); - prop_assume!(gossiped_addr.is_some()); - let gossiped_addr = gossiped_addr.unwrap(); - - // Check that malicious peers can't make Zebra's serialization fail - let addr_bytes = AddrV1::from(gossiped_addr).zcash_serialize_to_vec(); - prop_assert!( - addr_bytes.is_ok(), - "unexpected serialization error: {:?}, addr: {:?}", - addr_bytes, - gossiped_addr - ); - let addr_bytes = addr_bytes.unwrap(); - - // Assume other implementations deserialize like Zebra - let deserialized_addr = AddrV1::zcash_deserialize(addr_bytes.as_slice()); - prop_assert!( - deserialized_addr.is_ok(), - "unexpected deserialization error: {:?}, addr: {:?}, bytes: {:?}", - deserialized_addr, - gossiped_addr, - hex::encode(addr_bytes), - ); - let deserialized_addr = deserialized_addr.unwrap().into(); - - // Check that the addrs are equal - prop_assert_eq!( - gossiped_addr, - deserialized_addr, - "unexpected round-trip mismatch with bytes: {:?}", - hex::encode(addr_bytes), - ); - - // Now check that the re-serialized bytes are equal - // (`impl PartialEq for MetaAddr` might not match serialization equality) - let addr_bytes2 = AddrV1::from(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, - gossiped_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: {:?}", - gossiped_addr, - hex::encode(&addr_bytes), - deserialized_addr, - hex::encode(&addr_bytes2), - ); - } - /// Test round-trip AddrV1 serialization for all MetaAddr variants after sanitization #[test] - fn sanitized_roundtrip(addr in any::()) { + fn addr_v1_sanitized_roundtrip(addr in any::()) { zebra_test::init(); // We require sanitization before serialization, @@ -172,7 +115,10 @@ proptest! { // 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 + // Check that sanitization doesn't make Zebra's serialization fail. + // + // If this is a gossiped or DNS seeder address, + // we're also checking that malicious peers can't make Zebra's serialization fail. let addr_bytes = AddrV1::from(sanitized_addr).zcash_serialize_to_vec(); prop_assert!( addr_bytes.is_ok(), @@ -191,7 +137,7 @@ proptest! { sanitized_addr, hex::encode(addr_bytes), ); - let deserialized_addr = deserialized_addr.unwrap().into(); + let deserialized_addr: MetaAddr = deserialized_addr.unwrap().into(); // Check that the addrs are equal prop_assert_eq!( @@ -227,4 +173,78 @@ proptest! { hex::encode(&addr_bytes2), ); } + + /// Test round-trip AddrV2 serialization for all MetaAddr variants after sanitization + #[test] + fn addr_v2_sanitized_roundtrip(addr in any::()) { + zebra_test::init(); + + // We require sanitization before serialization, + // but we also need the original address for this test + let sanitized_addr = addr.sanitize(); + prop_assume!(sanitized_addr.is_some()); + let sanitized_addr = sanitized_addr.unwrap(); + + // 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. + // + // If this is a gossiped or DNS seeder address, + // we're also checking that malicious peers can't make Zebra's serialization fail. + let addr_bytes = AddrV2::from(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 = AddrV2::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: AddrV2 = deserialized_addr.unwrap(); + let deserialized_addr: MetaAddr = deserialized_addr.try_into().expect("arbitrary MetaAddrs are IPv4 or IPv6"); + + // 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 = AddrV2::from(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 double-serialization round-trip mismatch with original addr: {:?}, bytes: {:?}, deserialized addr: {:?}, bytes: {:?}", + sanitized_addr, + hex::encode(&addr_bytes), + deserialized_addr, + hex::encode(&addr_bytes2), + ); + } } diff --git a/zebra-network/src/protocol/external/tests/vectors.rs b/zebra-network/src/protocol/external/tests/vectors.rs index 965ba02c..226d69e2 100644 --- a/zebra-network/src/protocol/external/tests/vectors.rs +++ b/zebra-network/src/protocol/external/tests/vectors.rs @@ -1,14 +1,22 @@ -use std::io::Write; +//! Fixed test vectors for external protocol messages. + +use std::{convert::TryInto, io::Write}; use byteorder::{LittleEndian, WriteBytesExt}; +use chrono::{DateTime, Utc}; use zebra_chain::serialization::ZcashDeserializeInto; -use super::super::InventoryHash; +use crate::{ + meta_addr::MetaAddr, + protocol::external::{types::PeerServices, Codec, InventoryHash, Message}, +}; /// Test if deserializing [`InventoryHash::Wtx`] does not produce an error. #[test] fn parses_msg_wtx_inventory_type() { + zebra_test::init(); + let mut input = Vec::new(); input @@ -24,3 +32,246 @@ fn parses_msg_wtx_inventory_type() { assert_eq!(deserialized, InventoryHash::Wtx([0u8; 64].into())); } + +/// Test that deserializing [`AddrV1`] into [`MetaAddr`] succeeds, +/// and produces the expected number of addresses. +/// +/// Also checks some of the deserialized address values. +#[test] +fn parses_msg_addr_v1_ip() { + zebra_test::init(); + + let codec = Codec::builder().finish(); + + for (case_idx, addr_v1_bytes) in zebra_test::network_addr::ADDR_V1_IP_VECTORS + .iter() + .enumerate() + { + let deserialized: Message = codec + .read_addr(&mut addr_v1_bytes.as_slice()) + .unwrap_or_else(|_| panic!("failed to deserialize AddrV1 case {}", case_idx)); + + if let Message::Addr(addrs) = deserialized { + assert!( + !addrs.is_empty(), + "expected some AddrV1s in case {}: {:?}", + case_idx, + addrs + ); + assert!( + addrs.len() <= 2, + "too many AddrV1s in case {}: {:?}", + case_idx, + addrs + ); + + // Check all the fields in the first test case + if case_idx == 0 { + assert_eq!( + addrs, + vec![ + MetaAddr::new_gossiped_meta_addr( + "[::1]:0".parse().unwrap(), + PeerServices::empty(), + DateTime::parse_from_rfc3339("2009-01-09T02:54:25+00:00") + .unwrap() + .with_timezone(&Utc) + .try_into() + .unwrap(), + ), + MetaAddr::new_gossiped_meta_addr( + "[::1]:241".parse().unwrap(), + PeerServices::NODE_NETWORK, + DateTime::parse_from_rfc3339("2039-11-22T11:22:33+00:00") + .unwrap() + .with_timezone(&Utc) + .try_into() + .unwrap(), + ), + ], + ); + } + } else { + panic!( + "unexpected message variant in case {}: {:?}", + case_idx, deserialized + ); + } + } +} + +/// Test that deserializing empty [`AddrV1`] succeeds, +/// and produces no addresses. +#[test] +fn parses_msg_addr_v1_empty() { + zebra_test::init(); + + let codec = Codec::builder().finish(); + + for (case_idx, addr_v1_bytes) in zebra_test::network_addr::ADDR_V1_EMPTY_VECTORS + .iter() + .enumerate() + { + let deserialized: Message = codec + .read_addr(&mut addr_v1_bytes.as_slice()) + .unwrap_or_else(|_| panic!("failed to deserialize AddrV1 case {}", case_idx)); + + if let Message::Addr(addrs) = deserialized { + assert!( + addrs.is_empty(), + "expected empty AddrV1 list for case {}: {:?}", + case_idx, + addrs, + ); + } else { + panic!( + "unexpected message variant in case {}: {:?}", + case_idx, deserialized + ); + } + } +} + +/// Test that deserializing [`AddrV2`] into [`MetaAddr`] succeeds, +/// and produces the expected number of addresses. +/// +/// Also checks some of the deserialized address values. +#[test] +fn parses_msg_addr_v2_ip() { + zebra_test::init(); + + let codec = Codec::builder().finish(); + + for (case_idx, addr_v2_bytes) in zebra_test::network_addr::ADDR_V2_IP_VECTORS + .iter() + .enumerate() + { + let deserialized: Message = codec + .read_addrv2(&mut addr_v2_bytes.as_slice()) + .unwrap_or_else(|_| panic!("failed to deserialize AddrV2 case {}", case_idx)); + + if let Message::Addr(addrs) = deserialized { + assert!( + !addrs.is_empty(), + "expected some AddrV2s in case {}: {:?}", + case_idx, + addrs + ); + assert!( + addrs.len() <= 2, + "too many AddrV2s in case {}: {:?}", + case_idx, + addrs + ); + + // Check all the fields in the IPv4 and IPv6 test cases + if case_idx == 0 { + assert_eq!( + addrs, + vec![ + MetaAddr::new_gossiped_meta_addr( + "[::1]:0".parse().unwrap(), + PeerServices::empty(), + DateTime::parse_from_rfc3339("2009-01-09T02:54:25+00:00") + .unwrap() + .with_timezone(&Utc) + .try_into() + .unwrap(), + ), + MetaAddr::new_gossiped_meta_addr( + "[::1]:241".parse().unwrap(), + PeerServices::NODE_NETWORK, + DateTime::parse_from_rfc3339("2039-11-22T11:22:33+00:00") + .unwrap() + .with_timezone(&Utc) + .try_into() + .unwrap(), + ), + // torv3 is unsupported, so it's not in the parsed list + ], + ); + } else if case_idx == 1 { + assert_eq!( + addrs, + vec![ + MetaAddr::new_gossiped_meta_addr( + "127.0.0.1:1".parse().unwrap(), + PeerServices::NODE_NETWORK, + DateTime::parse_from_rfc3339("2039-11-22T11:22:33+00:00") + .unwrap() + .with_timezone(&Utc) + .try_into() + .unwrap(), + ), + MetaAddr::new_gossiped_meta_addr( + "[::1]:241".parse().unwrap(), + PeerServices::NODE_NETWORK, + DateTime::parse_from_rfc3339("2039-11-22T11:22:33+00:00") + .unwrap() + .with_timezone(&Utc) + .try_into() + .unwrap(), + ), + ], + ); + } + } else { + panic!( + "unexpected message variant in case {}: {:?}", + case_idx, deserialized + ); + } + } +} + +/// Test that deserializing empty [`AddrV2`] succeeds, +/// and produces no addresses. +#[test] +fn parses_msg_addr_v2_empty() { + zebra_test::init(); + + let codec = Codec::builder().finish(); + + for (case_idx, addr_v2_bytes) in zebra_test::network_addr::ADDR_V2_EMPTY_VECTORS + .iter() + .enumerate() + { + let deserialized: Message = codec + .read_addrv2(&mut addr_v2_bytes.as_slice()) + .unwrap_or_else(|_| panic!("failed to deserialize AddrV2 case {}", case_idx)); + + if let Message::Addr(addrs) = deserialized { + assert!( + addrs.is_empty(), + "expected empty AddrV2 list for case {}: {:?}", + case_idx, + addrs, + ); + } else { + panic!( + "unexpected message variant in case {}: {:?}", + case_idx, deserialized + ); + } + } +} + +/// Test that deserializing invalid [`AddrV2`] fails. +#[test] +fn parses_msg_addr_v2_invalid() { + zebra_test::init(); + + let codec = Codec::builder().finish(); + + for (case_idx, addr_v2_bytes) in zebra_test::network_addr::ADDR_V2_INVALID_VECTORS + .iter() + .enumerate() + { + codec + .read_addrv2(&mut addr_v2_bytes.as_slice()) + .expect_err(&format!( + "unexpected success: deserializing invalid AddrV2 case {} should have failed", + case_idx + )); + } +} diff --git a/zebra-test/src/lib.rs b/zebra-test/src/lib.rs index 4fb8a9ea..7516ec81 100644 --- a/zebra-test/src/lib.rs +++ b/zebra-test/src/lib.rs @@ -22,6 +22,7 @@ use std::sync::Once; pub mod command; pub mod mock_service; pub mod net; +pub mod network_addr; pub mod prelude; pub mod transcript; pub mod vectors; diff --git a/zebra-test/src/network_addr.rs b/zebra-test/src/network_addr.rs new file mode 100644 index 00000000..b95d6ff5 --- /dev/null +++ b/zebra-test/src/network_addr.rs @@ -0,0 +1,306 @@ +//! Contains test vectors for network protocol address messages: +//! * addr (v1): [addr Bitcoin Reference](https://developer.bitcoin.org/reference/p2p_networking.html#addr) +//! * addrv2: [ZIP-155](https://zips.z.cash/zip-0155#specification) +//! +//! These formats are deserialized into the [`zebra_network::Message::Addr`] variant. + +use hex::FromHex; +use lazy_static::lazy_static; + +lazy_static! { + /// Array of `addr` (v1) test vectors containing IP addresses. + /// + /// These test vectors can be read by [`zebra_network::protocol::external::Codec::read_addr`]. + /// They should produce successful results containing IP addresses. + // From https://github.com/zingolabs/zcash/blob/9ee66e423a3fbf4829ffeec354e82f4fbceff864/src/test/netbase_tests.cpp#L397 + pub static ref ADDR_V1_IP_VECTORS: Vec> = vec![ + // stream_addrv1_hex + >::from_hex( + concat!( + "02", // number of entries + + "61bc6649", // time, Fri Jan 9 02:54:25 UTC 2009 + "0000000000000000", // service flags, NODE_NONE + "00000000000000000000000000000001", // address, fixed 16 bytes (IPv4-mapped IPv6), ::1 + "0000", // port, 0 + + "79627683", // time, Tue Nov 22 11:22:33 UTC 2039 + "0100000000000000", // service flags, NODE_NETWORK + "00000000000000000000000000000001", // address, fixed 16 bytes (IPv4-mapped IPv6), ::1 + "00f1", // port, 241 + ) + ).expect("Message bytes are in valid hex representation"), + + // stream_torv3_incompatibly_serialized_to_v1 + >::from_hex( + concat!( + "01", // number of entries + + "79627683", // time, Tue Nov 22 11:22:33 UTC 2039 + "0100000000000000", // service flags, NODE_NETWORK + "00000000000000000000000000000000", // address, fixed 16 bytes (IPv4-mapped IPv6), :: + "235a", // port, 9050 + ) + ).expect("Message bytes are in valid hex representation"), + + // Extra test cases: + // + // all services flags set + >::from_hex( + concat!( + "01", // number of entries + + "61bc6649", // time, Fri Jan 9 02:54:25 UTC 2009 + "ffffffffffffffff", // service flags, all set + "00000000000000000000000000000001", // address, fixed 16 bytes (IPv4-mapped IPv6), ::1 + "0000", // port, 0 + ) + ).expect("Message bytes are in valid hex representation"), + + // IPv4 + >::from_hex( + concat!( + "01", // number of entries + + "61bc6649", // time, Fri Jan 9 02:54:25 UTC 2009 + "0100000000000000", // service flags, NODE_NETWORK + // address, fixed 16 bytes (IPv4-mapped IPv6), + "00000000000000000000ffff", // IPv4-mapped IPv6 prefix, ::ffff... + "7f000001", // IPv4, 127.0.0.1 + "0000", // port, 0 + ) + ).expect("Message bytes are in valid hex representation"), + + ]; + + /// Array of empty or unsupported `addr` (v1) test vectors. + /// + /// These test vectors can be read by [`zebra_network::protocol::external::Codec::read_addr`]. + /// They should produce successful but empty results. + pub static ref ADDR_V1_EMPTY_VECTORS: Vec> = vec![ + // Empty list + >::from_hex( + "00" // number of entries + ).expect("Message bytes are in valid hex representation"), + ]; + + /// Array of ZIP-155 test vectors containing IP addresses. + /// + /// Some test vectors also contain some unsupported addresses. + /// + /// These test vectors can be read by [`zebra_network::protocol::external::Codec::read_addrv2`], + /// They should produce successful results containing IP addresses. + // From https://github.com/zingolabs/zcash/blob/9ee66e423a3fbf4829ffeec354e82f4fbceff864/src/test/netbase_tests.cpp#L421 + pub static ref ADDR_V2_IP_VECTORS: Vec> = vec![ + // stream_addrv2_hex + >::from_hex( + concat!( + "03", // number of entries + + "61bc6649", // time, Fri Jan 9 02:54:25 UTC 2009 + "00", // service flags, COMPACTSIZE(NODE_NONE) + "02", // network id, IPv6 + "10", // address length, COMPACTSIZE(16) + "00000000000000000000000000000001", // address, ::1 + "0000", // port, 0 + + "79627683", // time, Tue Nov 22 11:22:33 UTC 2039 + "01", // service flags, COMPACTSIZE(NODE_NETWORK) + "02", // network id, IPv6 + "10", // address length, COMPACTSIZE(16) + "00000000000000000000000000000001", // address, ::1 + "00f1", // port, 241 + + "79627683", // time, Tue Nov 22 11:22:33 UTC 2039 + "01", // service flags, COMPACTSIZE(NODE_NETWORK) + "04", // network id, TorV3 + "20", // address length, COMPACTSIZE(32) + "53cd5648488c4707914182655b7664034e09e66f7e8cbf1084e654eb56c5bd88", + // address, (32 byte Tor v3 onion service public key) + "235a", // port, 9050 + ) + ).expect("Message bytes are in valid hex representation"), + + // Extra test cases: + // + // IPv4 + >::from_hex( + concat!( + "02", // number of entries + + "79627683", // time, Tue Nov 22 11:22:33 UTC 2039 + "01", // service flags, COMPACTSIZE(NODE_NETWORK) + "01", // network id, IPv4 + "04", // address length, COMPACTSIZE(4) + "7f000001", // address, 127.0.0.1 + "0001", // port, 1 + + // check that variable-length encoding works + "79627683", // time, Tue Nov 22 11:22:33 UTC 2039 + "01", // service flags, COMPACTSIZE(NODE_NETWORK) + "02", // network id, IPv6 + "10", // address length, COMPACTSIZE(16) + "00000000000000000000000000000001", // address, ::1 + "00f1", // port, 241 + ) + ).expect("Message bytes are in valid hex representation"), + + // all services flags set + >::from_hex( + concat!( + "01", // number of entries + + "79627683", // time, Tue Nov 22 11:22:33 UTC 2039 + "ffffffffffffffffff", // service flags, COMPACTSIZE(all flags set) + "02", // network id, IPv6 + "10", // address length, COMPACTSIZE(16) + "00000000000000000000000000000001", // address, ::1 + "0000", // port, 0 + ) + ).expect("Message bytes are in valid hex representation"), + + // Unknown Network ID: address within typical size range + >::from_hex( + concat!( + "02", // number of entries + + "79627683", // time, Tue Nov 22 11:22:33 UTC 2039 + "01", // service flags, COMPACTSIZE(NODE_NETWORK) + "fb", // network id, (unknown) + "08", // address length, COMPACTSIZE(8) + "0000000000000000", // address, (8 zero bytes) + "0001", // port, 1 + + // check that variable-length encoding works + "79627683", // time, Tue Nov 22 11:22:33 UTC 2039 + "01", // service flags, COMPACTSIZE(NODE_NETWORK) + "02", // network id, IPv6 + "10", // address length, COMPACTSIZE(16) + "00000000000000000000000000000001", // address, ::1 + "00f1", // port, 241 + ) + ).expect("Message bytes are in valid hex representation"), + + // Unknown Network ID: zero-sized address + >::from_hex( + concat!( + "02", // number of entries + + "79627683", // time, Tue Nov 22 11:22:33 UTC 2039 + "01", // service flags, COMPACTSIZE(NODE_NETWORK) + "fc", // network id, (unknown) + "00", // address length, COMPACTSIZE(0) + "", // address, (no bytes) + "0001", // port, 1 + + // check that variable-length encoding works + "79627683", // time, Tue Nov 22 11:22:33 UTC 2039 + "01", // service flags, COMPACTSIZE(NODE_NETWORK) + "02", // network id, IPv6 + "10", // address length, COMPACTSIZE(16) + "00000000000000000000000000000001", // address, ::1 + "00f1", // port, 241 + ) + ).expect("Message bytes are in valid hex representation"), + + // Unknown Network ID: maximum-sized address + >::from_hex( + format!("{}{}{}", + concat!( + "02", // number of entries + + "79627683", // time, Tue Nov 22 11:22:33 UTC 2039 + "01", // service flags, COMPACTSIZE(NODE_NETWORK) + "fd", // network id, (unknown) + "fd0002", // address length, COMPACTSIZE(512) + ), + "00".repeat(512), // address, (512 zero bytes) + concat!( + "0001", // port, 1 + + // check that variable-length encoding works + "79627683", // time, Tue Nov 22 11:22:33 UTC 2039 + "01", // service flags, COMPACTSIZE(NODE_NETWORK) + "02", // network id, IPv6 + "10", // address length, COMPACTSIZE(16) + "00000000000000000000000000000001", // address, ::1 + "00f1", // port, 241 + ) + ) + ).expect("Message bytes are in valid hex representation"), + ]; + + /// Array of empty or unsupported ZIP-155 test vectors. + /// + /// These test vectors can be read by [`zebra_network::protocol::external::Codec::read_addrv2`]. + /// They should produce successful but empty results. + // From https://github.com/zingolabs/zcash/blob/9ee66e423a3fbf4829ffeec354e82f4fbceff864/src/test/netbase_tests.cpp#L421 + pub static ref ADDR_V2_EMPTY_VECTORS: Vec> = vec![ + // torv3_hex + >::from_hex( + concat!( + "01", // number of entries + + "79627683", // time, Tue Nov 22 11:22:33 UTC 2039 + "01", // service flags, COMPACTSIZE(NODE_NETWORK) + "04", // network id, TorV3 + "20", // address length, COMPACTSIZE(32) + "53cd5648488c4707914182655b7664034e09e66f7e8cbf1084e654eb56c5bd88", + // address, (32 byte Tor v3 onion service public key) + "235a", // port, 9050 + ) + ).expect("Message bytes are in valid hex representation"), + + // Extra test cases: + // + // Empty list + >::from_hex( + "00" // number of entries + ).expect("Message bytes are in valid hex representation"), + ]; + + /// Array of invalid ZIP-155 test vectors. + /// + /// These test vectors can be read by [`zebra_network::protocol::external::Codec::read_addrv2`]. + /// They should fail deserialization. + pub static ref ADDR_V2_INVALID_VECTORS: Vec> = vec![ + // Invalid address size: too large, but under CompactSizeMessage limit + >::from_hex( + format!("{}{}{}", + concat!( + "02", // number of entries + + "79627683", // time, Tue Nov 22 11:22:33 UTC 2039 + "01", // service flags, COMPACTSIZE(NODE_NETWORK) + "fe", // network id, (unknown) + "fd0102", // invalid address length, COMPACTSIZE(513) + ), + "00".repeat(513), // address, (513 zero bytes) + concat!( + "0001", // port, 1 + + // check that the entire message is ignored + "79627683", // time, Tue Nov 22 11:22:33 UTC 2039 + "01", // service flags, COMPACTSIZE(NODE_NETWORK) + "02", // network id, IPv6 + "10", // address length, COMPACTSIZE(16) + "00000000000000000000000000000001", // address, ::1 + "00f1", // port, 241 + ) + ) + ).expect("Message bytes are in valid hex representation"), + + // Invalid address size: too large, over CompactSizeMessage limit + >::from_hex( + concat!( + "01", // number of entries + + "79627683", // time, Tue Nov 22 11:22:33 UTC 2039 + "01", // service flags, COMPACTSIZE(NODE_NETWORK) + "ff", // network id, (unknown) + "feffffff7f", // invalid address length, COMPACTSIZE(2^31 - 1) + // no address, generated bytes wouldn't fit in memory + ), + ).expect("Message bytes are in valid hex representation"), + ]; +} diff --git a/zebrad/src/components/inbound.rs b/zebrad/src/components/inbound.rs index 09724dca..6b02ec3c 100644 --- a/zebrad/src/components/inbound.rs +++ b/zebrad/src/components/inbound.rs @@ -18,7 +18,7 @@ use zebra_state as zs; use zebra_chain::block::{self, Block}; use zebra_consensus::chain::VerifyChainError; -use zebra_network::AddressBook; +use zebra_network::{constants::MAX_ADDRS_IN_MESSAGE, AddressBook}; // Re-use the syncer timeouts for consistency. use super::{ @@ -40,9 +40,6 @@ type InboundBlockDownloads = BlockDownloads, Timeout>, Mempool); -/// A bitcoin protocol constant that will hold the max number of peers -/// we can return in response to a `Peers` request. -const MAX_ADDR: usize = 1000; // bitcoin protocol constant /// A security parameter to return only 1/3 of available addresses as a /// response to a `Peers` request. const FRAC_OF_AVAILABLE_ADDRESS: f64 = 1. / 3.; @@ -270,7 +267,7 @@ impl Service for Inbound { let mut peers = peers.sanitized(); // Truncate the list - let truncate_at = MAX_ADDR + let truncate_at = MAX_ADDRS_IN_MESSAGE .min((peers.len() as f64 * FRAC_OF_AVAILABLE_ADDRESS).ceil() as usize); peers.truncate(truncate_at);