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:
parent
6570ebeeb8
commit
d6f3b3dc9a
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
];
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue