Parse received addrv2 messages (#3022)

* Revert "Remove commented-out code"

This reverts commit 9e69777925f103ee11e5940bba95b896c828839b.

* Implement deserialization for `addrv2` messages

* Limit addr and addrv2 messages to MAX_ADDRS_IN_MESSAGE

* Clarify address version comments

* Minor cleanups and fixes

* Add preallocation tests for AddrV2

* Add serialization tests for AddrV2

* Use prop_assert in AddrV2 proptests

* Use a generic utility method for deserializing IP addresses in `addrv2`

* Document the purpose of a conversion to MetaAddr

* Fix a comment typo, and clarify that comment

* Clarify the unsupported AddrV2 network ID error and enum variant names

```sh
fastmod AddrV2UnimplementedError UnsupportedAddrV2NetworkIdError zebra-network
fastmod Unimplemented Unsupported zebra-network
```

* Fix and clarify unsupported AddrV2 comments

* Replace `panic!` with `unreachable!`

* Clarify a comment about skipping a length check in a test

* Remove a redundant test

* Basic addr (v1) and addrv2 deserialization tests

* Test deserialized IPv4 and IPv6 values in addr messages

* Remove redundant io::Cursor

* Add comments with expected values of address test vectors
This commit is contained in:
teor 2021-11-12 10:25:23 +10:00 committed by GitHub
parent 6570ebeeb8
commit d6f3b3dc9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1103 additions and 95 deletions

View File

@ -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
}
}

View File

@ -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

View File

@ -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;

View File

@ -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)]

View File

@ -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<MetaAddr> for AddrV1 {
}
impl From<AddrV1> 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<AddrV1> 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
}
}

View File

@ -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<MetaAddr> 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<AddrV2> for MetaAddr {
type Error = UnsupportedAddrV2NetworkIdError;
fn try_from(addr: AddrV2) -> Result<MetaAddr, UnsupportedAddrV2NetworkIdError> {
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<const IP_ADDR_SIZE: usize>(
addr_bytes: Vec<u8>,
) -> Result<IpAddr, SerializationError>
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<W: Write>(&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::<BigEndian>(*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::<BigEndian>(*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<R: Read>(mut reader: R) -> Result<Self, SerializationError> {
// > 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<u8> = (&mut reader).zcash_deserialize_into()?;
// > uint16 Network port. If not relevant for the network this MUST be 0.
let port = reader.read_u16::<BigEndian>()?;
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_V2_IPV4_ADDR_SIZE>(addr)?
} else if network_id == ADDR_V2_IPV6_NETWORK_ID {
AddrV2::ip_addr_from_bytes::<ADDR_V2_IPV6_ADDR_SIZE>(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
}
}

View File

@ -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<AddrV1> = 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<R: Read>(&self, reader: R) -> Result<Message, Error> {
pub(super) fn read_addr<R: Read>(&self, reader: R) -> Result<Message, Error> {
let addrs: Vec<AddrV1> = 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<R: Read>(&self, reader: R) -> Result<Message, Error> {
let addrs: Vec<AddrV2> = 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<R: Read>(&self, mut _reader: R) -> Result<Message, Error> {
Ok(Message::GetAddr)
}

View File

@ -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<MetaAddr>),

View File

@ -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
}
}

View File

@ -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::<MetaAddr>()) {
fn addr_v1_sanitized_roundtrip(addr in any::<MetaAddr>()) {
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::<MetaAddr>()) {
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),
);
}
}

View File

@ -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
));
}
}

View File

@ -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;

View File

@ -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<u8>> = vec![
// stream_addrv1_hex
<Vec<u8>>::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
<Vec<u8>>::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
<Vec<u8>>::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
<Vec<u8>>::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<u8>> = vec![
// Empty list
<Vec<u8>>::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<u8>> = vec![
// stream_addrv2_hex
<Vec<u8>>::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
<Vec<u8>>::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
<Vec<u8>>::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
<Vec<u8>>::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
<Vec<u8>>::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
<Vec<u8>>::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<u8>> = vec![
// torv3_hex
<Vec<u8>>::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
<Vec<u8>>::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<u8>> = vec![
// Invalid address size: too large, but under CompactSizeMessage limit
<Vec<u8>>::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
<Vec<u8>>::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"),
];
}

View File

@ -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<Outbound>, Timeout<BlockVeri
pub type NetworkSetupData = (Outbound, Arc<std::sync::Mutex<AddressBook>>, 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<zn::Request> 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);