Refactor addr v1 serialization using a separate AddrV1 type (#3021)
* Implement addr v1 serialization using a separate AddrV1 type * Remove commented-out code * Split the address serialization code into modules * Reorder v1 and in_version fields in serialization order * Fix a missed search-and-replace * Explain conversion to MetaAddr Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com> Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com>
This commit is contained in:
parent
62bfa15e96
commit
85b016756d
|
@ -29,7 +29,7 @@ pub use compact_size::{CompactSize64, CompactSizeMessage};
|
|||
pub use constraint::AtLeastOne;
|
||||
pub use date_time::{DateTime32, Duration32};
|
||||
pub use error::SerializationError;
|
||||
pub use read_zcash::{canonical_socket_addr, ReadZcashExt};
|
||||
pub use read_zcash::ReadZcashExt;
|
||||
pub use write_zcash::WriteZcashExt;
|
||||
pub use zcash_deserialize::{
|
||||
zcash_deserialize_bytes_external_count, zcash_deserialize_external_count, TrustedPreallocate,
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
//! Arbitrary data generation for serialization proptests
|
||||
|
||||
use std::{convert::TryInto, net::SocketAddr};
|
||||
use std::convert::TryInto;
|
||||
|
||||
use chrono::{TimeZone, Utc, MAX_DATETIME, MIN_DATETIME};
|
||||
use proptest::{arbitrary::any, prelude::*};
|
||||
|
||||
use super::{
|
||||
read_zcash::canonical_socket_addr, CompactSizeMessage, DateTime32, MAX_PROTOCOL_MESSAGE_LEN,
|
||||
};
|
||||
use super::{CompactSizeMessage, DateTime32, MAX_PROTOCOL_MESSAGE_LEN};
|
||||
|
||||
impl Arbitrary for DateTime32 {
|
||||
type Parameters = ();
|
||||
|
@ -60,13 +58,6 @@ pub fn datetime_u32() -> impl Strategy<Value = chrono::DateTime<Utc>> {
|
|||
any::<DateTime32>().prop_map(Into::into)
|
||||
}
|
||||
|
||||
/// Returns a random canonical Zebra `SocketAddr`.
|
||||
///
|
||||
/// See [`canonical_ip_addr`] for details.
|
||||
pub fn canonical_socket_addr_strategy() -> impl Strategy<Value = SocketAddr> {
|
||||
any::<SocketAddr>().prop_map(canonical_socket_addr)
|
||||
}
|
||||
|
||||
impl Arbitrary for CompactSizeMessage {
|
||||
type Parameters = ();
|
||||
|
||||
|
|
|
@ -1,32 +1,9 @@
|
|||
use std::{
|
||||
io,
|
||||
net::{IpAddr, Ipv6Addr, SocketAddr},
|
||||
};
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use std::io;
|
||||
|
||||
/// Extends [`Read`] with methods for writing Zcash/Bitcoin types.
|
||||
///
|
||||
/// [`Read`]: https://doc.rust-lang.org/std/io/trait.Read.html
|
||||
pub trait ReadZcashExt: io::Read {
|
||||
/// Read an IP address in Bitcoin format.
|
||||
#[inline]
|
||||
fn read_ip_addr(&mut self) -> io::Result<IpAddr> {
|
||||
let mut octets = [0u8; 16];
|
||||
self.read_exact(&mut octets)?;
|
||||
let v6_addr = Ipv6Addr::from(octets);
|
||||
|
||||
Ok(canonical_ip_addr(&v6_addr))
|
||||
}
|
||||
|
||||
/// Read a Bitcoin-encoded `SocketAddr`.
|
||||
#[inline]
|
||||
fn read_socket_addr(&mut self) -> io::Result<SocketAddr> {
|
||||
let ip_addr = self.read_ip_addr()?;
|
||||
let port = self.read_u16::<BigEndian>()?;
|
||||
Ok(SocketAddr::new(ip_addr, port))
|
||||
}
|
||||
|
||||
/// Convenience method to read a `[u8; 4]`.
|
||||
#[inline]
|
||||
fn read_4_bytes(&mut self) -> io::Result<[u8; 4]> {
|
||||
|
@ -62,37 +39,3 @@ pub trait ReadZcashExt: io::Read {
|
|||
|
||||
/// Mark all types implementing `Read` as implementing the extension.
|
||||
impl<R: io::Read + ?Sized> ReadZcashExt for R {}
|
||||
|
||||
/// Transform a Zcash-deserialized IPv6 address into a canonical Zebra IP address.
|
||||
///
|
||||
/// Zcash uses IPv6-mapped IPv4 addresses in its network protocol. Zebra converts
|
||||
/// those addresses to `Ipv4Addr`s, for maximum compatibility with systems that
|
||||
/// don't understand IPv6.
|
||||
pub fn canonical_ip_addr(v6_addr: &Ipv6Addr) -> IpAddr {
|
||||
use IpAddr::*;
|
||||
|
||||
// TODO: replace with `to_ipv4_mapped` when that stabilizes
|
||||
// https://github.com/rust-lang/rust/issues/27709
|
||||
match v6_addr.to_ipv4() {
|
||||
// workaround for unstable `to_ipv4_mapped`
|
||||
Some(v4_addr) if v4_addr.to_ipv6_mapped() == *v6_addr => V4(v4_addr),
|
||||
Some(_) | None => V6(*v6_addr),
|
||||
}
|
||||
}
|
||||
|
||||
/// Transform a `SocketAddr` into a canonical Zebra `SocketAddr`, converting
|
||||
/// IPv6-mapped IPv4 addresses, and removing IPv6 scope IDs and flow information.
|
||||
///
|
||||
/// See [`canonical_ip_addr`] for detailed info on IPv6-mapped IPv4 addresses.
|
||||
pub fn canonical_socket_addr(socket_addr: impl Into<SocketAddr>) -> SocketAddr {
|
||||
use SocketAddr::*;
|
||||
|
||||
let mut socket_addr = socket_addr.into();
|
||||
if let V6(v6_socket_addr) = socket_addr {
|
||||
let canonical_ip = canonical_ip_addr(v6_socket_addr.ip());
|
||||
// creating a new SocketAddr removes scope IDs and flow information
|
||||
socket_addr = SocketAddr::new(canonical_ip, socket_addr.port());
|
||||
}
|
||||
|
||||
socket_addr
|
||||
}
|
||||
|
|
|
@ -1,32 +1,9 @@
|
|||
use std::{
|
||||
io,
|
||||
net::{IpAddr, SocketAddr},
|
||||
};
|
||||
|
||||
use byteorder::{BigEndian, WriteBytesExt};
|
||||
use std::io;
|
||||
|
||||
/// Extends [`Write`] with methods for writing Zcash/Bitcoin types.
|
||||
///
|
||||
/// [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
|
||||
pub trait WriteZcashExt: io::Write {
|
||||
/// Write an `IpAddr` in Bitcoin format.
|
||||
#[inline]
|
||||
fn write_ip_addr(&mut self, addr: IpAddr) -> io::Result<()> {
|
||||
use std::net::IpAddr::*;
|
||||
let v6_addr = match addr {
|
||||
V4(ref v4) => v4.to_ipv6_mapped(),
|
||||
V6(v6) => v6,
|
||||
};
|
||||
self.write_all(&v6_addr.octets())
|
||||
}
|
||||
|
||||
/// Write a `SocketAddr` in Bitcoin format.
|
||||
#[inline]
|
||||
fn write_socket_addr(&mut self, addr: SocketAddr) -> io::Result<()> {
|
||||
self.write_ip_addr(addr.ip())?;
|
||||
self.write_u16::<BigEndian>(addr.port())
|
||||
}
|
||||
|
||||
/// Convenience method to write exactly 32 u8's.
|
||||
#[inline]
|
||||
fn write_32_bytes(&mut self, bytes: &[u8; 32]) -> io::Result<()> {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
io,
|
||||
net::Ipv6Addr,
|
||||
};
|
||||
|
||||
use super::{AtLeastOne, CompactSizeMessage, SerializationError, MAX_PROTOCOL_MESSAGE_LEN};
|
||||
|
@ -128,6 +129,19 @@ impl ZcashDeserialize for String {
|
|||
}
|
||||
}
|
||||
|
||||
// We don't impl ZcashDeserialize for Ipv4Addr or SocketAddrs,
|
||||
// because the IPv4 and port formats are different in addr (v1) and addrv2 messages.
|
||||
|
||||
/// Read a Bitcoin-encoded IPv6 address.
|
||||
impl ZcashDeserialize for Ipv6Addr {
|
||||
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
||||
let mut ipv6_addr = [0u8; 16];
|
||||
reader.read_exact(&mut ipv6_addr)?;
|
||||
|
||||
Ok(Ipv6Addr::from(ipv6_addr))
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper for deserializing more succinctly via type inference
|
||||
pub trait ZcashDeserializeInto {
|
||||
/// Deserialize based on type inference
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{convert::TryInto, io};
|
||||
use std::{convert::TryInto, io, net::Ipv6Addr};
|
||||
|
||||
use super::{AtLeastOne, CompactSizeMessage};
|
||||
|
||||
|
@ -174,3 +174,13 @@ impl ZcashSerialize for String {
|
|||
self.as_str().zcash_serialize(&mut writer)
|
||||
}
|
||||
}
|
||||
|
||||
// We don't impl ZcashSerialize for Ipv4Addr or SocketAddrs,
|
||||
// because the IPv4 and port formats are different in addr (v1) and addrv2 messages.
|
||||
|
||||
/// Write a Bitcoin-encoded IPv6 address.
|
||||
impl ZcashSerialize for Ipv6Addr {
|
||||
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), std::io::Error> {
|
||||
writer.write_all(&self.octets())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,9 +10,10 @@ use std::{
|
|||
|
||||
use tracing::Span;
|
||||
|
||||
use zebra_chain::serialization::canonical_socket_addr;
|
||||
|
||||
use crate::{meta_addr::MetaAddrChange, types::MetaAddr, PeerAddrState};
|
||||
use crate::{
|
||||
meta_addr::MetaAddrChange, protocol::external::canonical_socket_addr, types::MetaAddr,
|
||||
PeerAddrState,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
|
|
@ -7,9 +7,9 @@ use std::{
|
|||
|
||||
use serde::{de, Deserialize, Deserializer};
|
||||
|
||||
use zebra_chain::{parameters::Network, serialization::canonical_socket_addr};
|
||||
use zebra_chain::parameters::Network;
|
||||
|
||||
use crate::{constants, BoxError};
|
||||
use crate::{constants, protocol::external::canonical_socket_addr, BoxError};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
|
|
@ -98,20 +98,26 @@ impl Service<Request> for Wrapper {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn connect_isolated_sends_minimally_distinguished_version_message() {
|
||||
use crate::{
|
||||
protocol::external::{Codec, Message},
|
||||
types::PeerServices,
|
||||
};
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use futures::stream::StreamExt;
|
||||
use tokio_util::codec::Framed;
|
||||
|
||||
use crate::{
|
||||
protocol::external::{AddrInVersion, Codec, Message},
|
||||
types::PeerServices,
|
||||
};
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let listen_addr = listener.local_addr().unwrap();
|
||||
|
||||
let fixed_isolated_addr: SocketAddr = "0.0.0.0:8233".parse().unwrap();
|
||||
|
||||
let conn = tokio::net::TcpStream::connect(listen_addr).await.unwrap();
|
||||
|
||||
tokio::spawn(connect_isolated(conn, "".to_string()));
|
||||
|
@ -139,7 +145,7 @@ mod tests {
|
|||
assert_eq!(timestamp.timestamp() % (5 * 60), 0);
|
||||
assert_eq!(
|
||||
address_from,
|
||||
(PeerServices::empty(), "0.0.0.0:8233".parse().unwrap())
|
||||
AddrInVersion::new(fixed_isolated_addr, PeerServices::empty()),
|
||||
);
|
||||
assert_eq!(user_agent, "");
|
||||
assert_eq!(start_height.0, 0);
|
||||
|
|
|
@ -7,22 +7,27 @@ use std::{
|
|||
time::Instant,
|
||||
};
|
||||
|
||||
use zebra_chain::serialization::{canonical_socket_addr, DateTime32};
|
||||
use zebra_chain::serialization::DateTime32;
|
||||
|
||||
use crate::{constants, protocol::types::PeerServices};
|
||||
use crate::{
|
||||
constants,
|
||||
protocol::{external::canonical_socket_addr, types::PeerServices},
|
||||
};
|
||||
|
||||
use MetaAddrChange::*;
|
||||
use PeerAddrState::*;
|
||||
|
||||
#[cfg(any(test, feature = "proptest-impl"))]
|
||||
use proptest_derive::Arbitrary;
|
||||
|
||||
#[cfg(any(test, feature = "proptest-impl"))]
|
||||
use zebra_chain::serialization::arbitrary::canonical_socket_addr_strategy;
|
||||
use crate::protocol::external::arbitrary::canonical_socket_addr_strategy;
|
||||
|
||||
#[cfg(any(test, feature = "proptest-impl"))]
|
||||
pub(crate) mod arbitrary;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
pub(crate) mod tests;
|
||||
|
||||
/// Peer connection state, based on our interactions with the peer.
|
||||
///
|
||||
|
@ -112,6 +117,8 @@ impl PartialOrd for PeerAddrState {
|
|||
|
||||
/// An address with metadata on its advertised services and last-seen time.
|
||||
///
|
||||
/// This struct can be created from `addr` or `addrv2` messages.
|
||||
///
|
||||
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#Network_address)
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
|
||||
|
@ -131,8 +138,8 @@ pub struct MetaAddr {
|
|||
/// The exact meaning depends on `last_connection_state`:
|
||||
/// - `Responded`: the services advertised by this peer, the last time we
|
||||
/// performed a handshake with it
|
||||
/// - `NeverAttempted`: the unverified services provided by the remote peer
|
||||
/// that sent us this address
|
||||
/// - `NeverAttempted`: the unverified services advertised by another peer,
|
||||
/// then gossiped by the peer that sent us this address
|
||||
/// - `Failed` or `AttemptPending`: unverified services via another peer,
|
||||
/// or services advertised in a previous handshake
|
||||
///
|
||||
|
|
|
@ -2,9 +2,11 @@ use std::net::SocketAddr;
|
|||
|
||||
use proptest::{arbitrary::any, collection::vec, prelude::*};
|
||||
|
||||
use super::{MetaAddr, MetaAddrChange, PeerServices};
|
||||
use zebra_chain::serialization::DateTime32;
|
||||
|
||||
use zebra_chain::serialization::{arbitrary::canonical_socket_addr_strategy, DateTime32};
|
||||
use crate::protocol::external::arbitrary::canonical_socket_addr_strategy;
|
||||
|
||||
use super::{MetaAddr, MetaAddrChange, PeerServices};
|
||||
|
||||
/// The largest number of random changes we want to apply to a [`MetaAddr`].
|
||||
///
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
mod check;
|
||||
//! Tests for MetaAddrs
|
||||
|
||||
pub(crate) mod check;
|
||||
|
||||
mod prop;
|
||||
mod vectors;
|
||||
|
|
|
@ -15,9 +15,6 @@ use tokio::{runtime, time::Instant};
|
|||
use tower::service_fn;
|
||||
use tracing::Span;
|
||||
|
||||
use zebra_chain::serialization::{canonical_socket_addr, ZcashDeserialize, ZcashSerialize};
|
||||
|
||||
use super::check;
|
||||
use crate::{
|
||||
constants::MIN_PEER_RECONNECTION_DELAY,
|
||||
meta_addr::{
|
||||
|
@ -26,10 +23,12 @@ use crate::{
|
|||
PeerAddrState::*,
|
||||
},
|
||||
peer_set::candidate_set::CandidateSet,
|
||||
protocol::types::PeerServices,
|
||||
protocol::{external::canonical_socket_addr, types::PeerServices},
|
||||
AddressBook,
|
||||
};
|
||||
|
||||
use super::check;
|
||||
|
||||
/// The number of test cases to use for proptest that have verbose failures.
|
||||
///
|
||||
/// Set this to the default number of proptest cases, unless you're debugging a
|
||||
|
@ -61,139 +60,6 @@ proptest! {
|
|||
}
|
||||
}
|
||||
|
||||
/// Test round-trip 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 = 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 = MetaAddr::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();
|
||||
|
||||
// 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 = 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 serialization for all MetaAddr variants after sanitization
|
||||
#[test]
|
||||
fn 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
|
||||
let addr_bytes = sanitized_addr.zcash_serialize_to_vec();
|
||||
prop_assert!(
|
||||
addr_bytes.is_ok(),
|
||||
"unexpected serialization error: {:?}, addr: {:?}",
|
||||
addr_bytes,
|
||||
sanitized_addr
|
||||
);
|
||||
let addr_bytes = addr_bytes.unwrap();
|
||||
|
||||
// Assume other implementations deserialize like Zebra
|
||||
let deserialized_addr = MetaAddr::zcash_deserialize(addr_bytes.as_slice());
|
||||
prop_assert!(
|
||||
deserialized_addr.is_ok(),
|
||||
"unexpected deserialization error: {:?}, addr: {:?}, bytes: {:?}",
|
||||
deserialized_addr,
|
||||
sanitized_addr,
|
||||
hex::encode(addr_bytes),
|
||||
);
|
||||
let deserialized_addr = deserialized_addr.unwrap();
|
||||
|
||||
// Check that the addrs are equal
|
||||
prop_assert_eq!(
|
||||
sanitized_addr,
|
||||
deserialized_addr,
|
||||
"unexpected round-trip mismatch with bytes: {:?}",
|
||||
hex::encode(addr_bytes),
|
||||
);
|
||||
|
||||
// Check that serialization hasn't de-sanitized anything
|
||||
check::sanitize_avoids_leaks(&addr, &deserialized_addr);
|
||||
|
||||
// Now check that the re-serialized bytes are equal
|
||||
// (`impl PartialEq for MetaAddr` might not match serialization equality)
|
||||
let addr_bytes2 = deserialized_addr.zcash_serialize_to_vec();
|
||||
prop_assert!(
|
||||
addr_bytes2.is_ok(),
|
||||
"unexpected serialization error after round-trip: {:?}, original addr: {:?}, bytes: {:?}, deserialized addr: {:?}",
|
||||
addr_bytes2,
|
||||
sanitized_addr,
|
||||
hex::encode(addr_bytes),
|
||||
deserialized_addr,
|
||||
);
|
||||
let addr_bytes2 = addr_bytes2.unwrap();
|
||||
|
||||
prop_assert_eq!(
|
||||
&addr_bytes,
|
||||
&addr_bytes2,
|
||||
"unexpected double-serialization round-trip mismatch with original addr: {:?}, bytes: {:?}, deserialized addr: {:?}, bytes: {:?}",
|
||||
sanitized_addr,
|
||||
hex::encode(&addr_bytes),
|
||||
deserialized_addr,
|
||||
hex::encode(&addr_bytes2),
|
||||
);
|
||||
}
|
||||
|
||||
/// Make sure that [`MetaAddrChange`]s:
|
||||
/// - do not modify the last seen time, unless it was None, and
|
||||
/// - only modify the services after a response or failure.
|
||||
|
|
|
@ -33,7 +33,7 @@ use crate::{
|
|||
peer::{Client, ClientRequest, Connection, ErrorSlot, HandshakeError, PeerError},
|
||||
peer_set::ConnectionTracker,
|
||||
protocol::{
|
||||
external::{types::*, Codec, InventoryHash, Message},
|
||||
external::{types::*, AddrInVersion, Codec, InventoryHash, Message},
|
||||
internal::{Request, Response},
|
||||
},
|
||||
types::MetaAddr,
|
||||
|
@ -523,9 +523,9 @@ pub async fn negotiate_version(
|
|||
version: constants::CURRENT_NETWORK_PROTOCOL_VERSION,
|
||||
services: our_services,
|
||||
timestamp,
|
||||
address_recv: (PeerServices::NODE_NETWORK, their_addr),
|
||||
address_recv: AddrInVersion::new(their_addr, PeerServices::NODE_NETWORK),
|
||||
// TODO: detect external address (#1893)
|
||||
address_from: (our_services, our_listen_addr),
|
||||
address_from: AddrInVersion::new(our_listen_addr, our_services),
|
||||
nonce: local_nonce,
|
||||
user_agent: user_agent.clone(),
|
||||
// The protocol works fine if we don't reveal our current block height,
|
||||
|
@ -554,7 +554,8 @@ pub async fn negotiate_version(
|
|||
..
|
||||
} = remote_msg
|
||||
{
|
||||
let (address_services, canonical_addr) = address_from;
|
||||
let canonical_addr = address_from.addr();
|
||||
let address_services = address_from.untrusted_services();
|
||||
if address_services != services {
|
||||
info!(
|
||||
?services,
|
||||
|
|
|
@ -12,10 +12,11 @@ mod message;
|
|||
pub mod types;
|
||||
|
||||
#[cfg(any(test, feature = "proptest-impl"))]
|
||||
mod arbitrary;
|
||||
pub mod arbitrary;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use addr::{canonical_socket_addr, AddrInVersion};
|
||||
pub use codec::Codec;
|
||||
pub use inv::InventoryHash;
|
||||
pub use message::Message;
|
||||
|
|
|
@ -1,65 +1,22 @@
|
|||
//! Node address types and serialization for the Zcash wire format.
|
||||
//! Zcash node address types and serialization for Zcash network messages.
|
||||
//!
|
||||
//! Zcash has 3 different node address formats:
|
||||
//! - [`AddrV1`]: the format used in `addr` (v1) messages,
|
||||
//! - [`AddrInVersion`]: the format used in `version` messages, and
|
||||
//! - [`AddrV2`]: the format used in `addrv2` messages.
|
||||
|
||||
use std::io::{Read, Write};
|
||||
pub mod canonical;
|
||||
pub mod in_version;
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
pub(crate) mod v1;
|
||||
|
||||
use zebra_chain::serialization::{
|
||||
ReadZcashExt, SerializationError, TrustedPreallocate, WriteZcashExt, ZcashDeserialize,
|
||||
ZcashDeserializeInto, ZcashSerialize,
|
||||
};
|
||||
pub use canonical::canonical_socket_addr;
|
||||
pub use in_version::AddrInVersion;
|
||||
|
||||
use crate::{
|
||||
meta_addr::MetaAddr,
|
||||
protocol::external::{types::PeerServices, MAX_PROTOCOL_MESSAGE_LEN},
|
||||
};
|
||||
// These types and functions should only be visible in the `external` module,
|
||||
// so that they don't leak outside the serialization code.
|
||||
|
||||
impl ZcashSerialize for MetaAddr {
|
||||
fn zcash_serialize<W: Write>(&self, mut writer: W) -> Result<(), std::io::Error> {
|
||||
self.last_seen()
|
||||
.expect(
|
||||
"unexpected MetaAddr with missing last seen time: MetaAddrs should be sanitized \
|
||||
before serialization",
|
||||
)
|
||||
.zcash_serialize(&mut writer)?;
|
||||
pub(super) use v1::AddrV1;
|
||||
|
||||
writer.write_u64::<LittleEndian>(
|
||||
self.services
|
||||
.expect(
|
||||
"unexpected MetaAddr with missing peer services: MetaAddrs should be \
|
||||
sanitized before serialization",
|
||||
)
|
||||
.bits(),
|
||||
)?;
|
||||
|
||||
writer.write_socket_addr(self.addr)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ZcashDeserialize for MetaAddr {
|
||||
fn zcash_deserialize<R: Read>(mut reader: R) -> Result<Self, SerializationError> {
|
||||
let untrusted_last_seen = (&mut reader).zcash_deserialize_into()?;
|
||||
let untrusted_services =
|
||||
PeerServices::from_bits_truncate(reader.read_u64::<LittleEndian>()?);
|
||||
let addr = reader.read_socket_addr()?;
|
||||
|
||||
Ok(MetaAddr::new_gossiped_meta_addr(
|
||||
addr,
|
||||
untrusted_services,
|
||||
untrusted_last_seen,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// A serialized meta addr has a 4 byte time, 8 byte services, 16 byte IP addr, and 2 byte port
|
||||
pub(super) const META_ADDR_SIZE: usize = 4 + 8 + 16 + 2;
|
||||
|
||||
impl TrustedPreallocate for MetaAddr {
|
||||
fn max_allocation() -> u64 {
|
||||
// Since a maximal serialized Vec<MetAddr> uses at least three bytes for its length (2MB messages / 30B MetaAddr implies the maximal length is much greater than 253)
|
||||
// the max allocation can never exceed (MAX_PROTOCOL_MESSAGE_LEN - 3) / META_ADDR_SIZE
|
||||
((MAX_PROTOCOL_MESSAGE_LEN - 3) / META_ADDR_SIZE) as u64
|
||||
}
|
||||
}
|
||||
#[cfg(any(test, feature = "proptest-impl"))]
|
||||
pub(super) use v1::{ipv6_mapped_socket_addr, ADDR_V1_SIZE};
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
//! Zebra's canonical node address format.
|
||||
//!
|
||||
//! Zebra canonicalises all received addresses into Rust [`SocketAddr`]s.
|
||||
//! If the address is an [IPv4-mapped IPv6 address], it becomes a [`SocketAddr::V4`]
|
||||
//!
|
||||
//! [IPv4-mapped IPv6 address]: https://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses
|
||||
|
||||
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
|
||||
|
||||
/// Transform a Zcash-deserialized IPv6 address into a canonical Zebra IP address.
|
||||
///
|
||||
/// Zcash uses IPv6-mapped IPv4 addresses in its `addr` (v1) network messages.
|
||||
/// Zebra converts those addresses to `Ipv4Addr`s, for maximum compatibility
|
||||
/// with systems that don't understand IPv6.
|
||||
///
|
||||
/// Zebra also uses this canonical format for addresses from other sources.
|
||||
pub fn canonical_ip_addr(v6_addr: &Ipv6Addr) -> IpAddr {
|
||||
use IpAddr::*;
|
||||
|
||||
// TODO: replace with `to_ipv4_mapped` when that stabilizes
|
||||
// https://github.com/rust-lang/rust/issues/27709
|
||||
match v6_addr.to_ipv4() {
|
||||
// workaround for unstable `to_ipv4_mapped`
|
||||
Some(v4_addr) if v4_addr.to_ipv6_mapped() == *v6_addr => V4(v4_addr),
|
||||
Some(_) | None => V6(*v6_addr),
|
||||
}
|
||||
}
|
||||
|
||||
/// Transform a `SocketAddr` into a canonical Zebra `SocketAddr`, converting
|
||||
/// IPv6-mapped IPv4 addresses, and removing IPv6 scope IDs and flow information.
|
||||
///
|
||||
/// See [`canonical_ip_addr`] for detailed info on IPv6-mapped IPv4 addresses.
|
||||
pub fn canonical_socket_addr(socket_addr: impl Into<SocketAddr>) -> SocketAddr {
|
||||
use SocketAddr::*;
|
||||
|
||||
let mut socket_addr = socket_addr.into();
|
||||
if let V6(v6_socket_addr) = socket_addr {
|
||||
let canonical_ip = canonical_ip_addr(v6_socket_addr.ip());
|
||||
// creating a new SocketAddr removes scope IDs and flow information
|
||||
socket_addr = SocketAddr::new(canonical_ip, socket_addr.port());
|
||||
}
|
||||
|
||||
socket_addr
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
//! Zcash `version` message node address serialization.
|
||||
//!
|
||||
//! The [`AddrInVersion`] format is the same as the `addr` ([`v1`]) message,
|
||||
//! but without the timestamp field.
|
||||
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
net::{SocketAddr, SocketAddrV6},
|
||||
};
|
||||
|
||||
use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use zebra_chain::serialization::{
|
||||
SerializationError, ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize,
|
||||
};
|
||||
|
||||
use crate::protocol::external::types::PeerServices;
|
||||
|
||||
#[cfg(any(test, feature = "proptest-impl"))]
|
||||
use proptest_derive::Arbitrary;
|
||||
|
||||
#[cfg(any(test, feature = "proptest-impl"))]
|
||||
use crate::protocol::external::arbitrary::addr_v1_ipv6_mapped_socket_addr_strategy;
|
||||
|
||||
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.
|
||||
///
|
||||
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#Network_address)
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
|
||||
pub struct AddrInVersion {
|
||||
/// The unverified services for the peer at `ipv6_addr`.
|
||||
///
|
||||
/// These services were advertised by the peer at `ipv6_addr`,
|
||||
/// 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 IPv6 socket address.
|
||||
/// IPv4 addresses are serialized as an [IPv4-mapped IPv6 address].
|
||||
///
|
||||
/// [IPv4-mapped IPv6 address]: https://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses
|
||||
#[cfg_attr(
|
||||
any(test, feature = "proptest-impl"),
|
||||
proptest(strategy = "addr_v1_ipv6_mapped_socket_addr_strategy()")
|
||||
)]
|
||||
ipv6_addr: SocketAddrV6,
|
||||
}
|
||||
|
||||
impl AddrInVersion {
|
||||
/// Returns a new `version` message address based on its fields.
|
||||
pub fn new(socket_addr: impl Into<SocketAddr>, untrusted_services: PeerServices) -> Self {
|
||||
Self {
|
||||
untrusted_services,
|
||||
ipv6_addr: ipv6_mapped_socket_addr(socket_addr),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the canonical address for this peer.
|
||||
pub fn addr(&self) -> SocketAddr {
|
||||
canonical_socket_addr(self.ipv6_addr)
|
||||
}
|
||||
|
||||
/// Returns the services for this peer.
|
||||
pub fn untrusted_services(&self) -> PeerServices {
|
||||
self.untrusted_services
|
||||
}
|
||||
}
|
||||
|
||||
impl ZcashSerialize for AddrInVersion {
|
||||
fn zcash_serialize<W: Write>(&self, mut writer: W) -> Result<(), std::io::Error> {
|
||||
writer.write_u64::<LittleEndian>(self.untrusted_services.bits())?;
|
||||
|
||||
self.ipv6_addr.ip().zcash_serialize(&mut writer)?;
|
||||
writer.write_u16::<BigEndian>(self.ipv6_addr.port())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ZcashDeserialize for AddrInVersion {
|
||||
fn zcash_deserialize<R: Read>(mut reader: R) -> Result<Self, SerializationError> {
|
||||
let untrusted_services =
|
||||
PeerServices::from_bits_truncate(reader.read_u64::<LittleEndian>()?);
|
||||
|
||||
let ipv6_addr = (&mut reader).zcash_deserialize_into()?;
|
||||
let port = reader.read_u16::<BigEndian>()?;
|
||||
|
||||
// `0` is the default unspecified value for these fields.
|
||||
let ipv6_addr = SocketAddrV6::new(ipv6_addr, port, 0, 0);
|
||||
|
||||
Ok(AddrInVersion {
|
||||
ipv6_addr,
|
||||
untrusted_services,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
//! Zcash `addr` (v1) message node address serialization.
|
||||
//!
|
||||
//! The [`AddrV1`] format serializes all IP addresses as IPv6 addresses.
|
||||
//! IPv4 addresses are converted to an [IPv4-mapped IPv6 address] before serialization.
|
||||
//!
|
||||
//! [IPv4-mapped IPv6 address]: https://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses
|
||||
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
net::{IpAddr, Ipv6Addr, SocketAddr, SocketAddrV6},
|
||||
};
|
||||
|
||||
use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use zebra_chain::serialization::{
|
||||
DateTime32, SerializationError, TrustedPreallocate, ZcashDeserialize, ZcashDeserializeInto,
|
||||
ZcashSerialize,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
meta_addr::MetaAddr,
|
||||
protocol::external::{types::PeerServices, MAX_PROTOCOL_MESSAGE_LEN},
|
||||
};
|
||||
|
||||
#[cfg(any(test, feature = "proptest-impl"))]
|
||||
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.
|
||||
///
|
||||
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#Network_address)
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
|
||||
pub(in super::super) struct AddrV1 {
|
||||
/// 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 `ipv6_addr`.
|
||||
///
|
||||
/// These services were advertised by the peer at `ipv6_addr`,
|
||||
/// 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 IPv6 socket address.
|
||||
/// IPv4 addresses are serialized as an [IPv4-mapped IPv6 address].
|
||||
///
|
||||
/// [IPv4-mapped IPv6 address]: https://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses
|
||||
#[cfg_attr(
|
||||
any(test, feature = "proptest-impl"),
|
||||
proptest(strategy = "addr_v1_ipv6_mapped_socket_addr_strategy()")
|
||||
)]
|
||||
ipv6_addr: SocketAddrV6,
|
||||
}
|
||||
|
||||
impl From<MetaAddr> for AddrV1 {
|
||||
fn from(meta_addr: MetaAddr) -> Self {
|
||||
let ipv6_addr = ipv6_mapped_socket_addr(meta_addr.addr);
|
||||
|
||||
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",
|
||||
);
|
||||
|
||||
AddrV1 {
|
||||
untrusted_last_seen,
|
||||
untrusted_services,
|
||||
ipv6_addr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AddrV1> for MetaAddr {
|
||||
fn from(addr_v1: AddrV1) -> Self {
|
||||
MetaAddr::new_gossiped_meta_addr(
|
||||
addr_v1.ipv6_addr.into(),
|
||||
addr_v1.untrusted_services,
|
||||
addr_v1.untrusted_last_seen,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ZcashSerialize for AddrV1 {
|
||||
fn zcash_serialize<W: Write>(&self, mut writer: W) -> Result<(), std::io::Error> {
|
||||
self.untrusted_last_seen.zcash_serialize(&mut writer)?;
|
||||
writer.write_u64::<LittleEndian>(self.untrusted_services.bits())?;
|
||||
|
||||
self.ipv6_addr.ip().zcash_serialize(&mut writer)?;
|
||||
writer.write_u16::<BigEndian>(self.ipv6_addr.port())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ZcashDeserialize for AddrV1 {
|
||||
fn zcash_deserialize<R: Read>(mut reader: R) -> Result<Self, SerializationError> {
|
||||
let untrusted_last_seen = (&mut reader).zcash_deserialize_into()?;
|
||||
let untrusted_services =
|
||||
PeerServices::from_bits_truncate(reader.read_u64::<LittleEndian>()?);
|
||||
|
||||
let ipv6_addr = (&mut reader).zcash_deserialize_into()?;
|
||||
let port = reader.read_u16::<BigEndian>()?;
|
||||
|
||||
// `0` is the default unspecified value for these fields.
|
||||
let ipv6_addr = SocketAddrV6::new(ipv6_addr, port, 0, 0);
|
||||
|
||||
Ok(AddrV1 {
|
||||
ipv6_addr,
|
||||
untrusted_services,
|
||||
untrusted_last_seen,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A serialized `addr` (v1) has a 4 byte time, 8 byte services, 16 byte IP addr, and 2 byte port
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// Transform a `SocketAddr` into an IPv6-mapped IPv4 addresses.
|
||||
///
|
||||
/// See [`canonical_ip_addr`] for detailed info on IPv6-mapped IPv4 addresses.
|
||||
pub(in super::super) fn ipv6_mapped_ip_addr(ip_addr: &IpAddr) -> Ipv6Addr {
|
||||
use IpAddr::*;
|
||||
|
||||
match ip_addr {
|
||||
V4(v4_addr) => v4_addr.to_ipv6_mapped(),
|
||||
V6(v6_addr) => *v6_addr,
|
||||
}
|
||||
}
|
||||
|
||||
/// Transform a `SocketAddr` into an IPv6-mapped IPv4 addresses,
|
||||
/// for `addr` (v1) Zcash network messages.
|
||||
///
|
||||
/// Also remove IPv6 scope IDs and flow information.
|
||||
///
|
||||
/// See [`canonical_ip_addr`] for detailed info on IPv6-mapped IPv4 addresses.
|
||||
pub(in super::super) fn ipv6_mapped_socket_addr(
|
||||
socket_addr: impl Into<SocketAddr>,
|
||||
) -> SocketAddrV6 {
|
||||
let socket_addr = socket_addr.into();
|
||||
|
||||
let ipv6_mapped_ip = ipv6_mapped_ip_addr(&socket_addr.ip());
|
||||
|
||||
// Remove scope IDs and flow information.
|
||||
// `0` is the default unspecified value for these fields.
|
||||
SocketAddrV6::new(ipv6_mapped_ip, socket_addr.port(), 0, 0)
|
||||
}
|
|
@ -1,11 +1,18 @@
|
|||
use std::convert::TryInto;
|
||||
use std::{
|
||||
convert::TryInto,
|
||||
net::{SocketAddr, SocketAddrV6},
|
||||
};
|
||||
|
||||
use proptest::{arbitrary::any, arbitrary::Arbitrary, collection::vec, prelude::*};
|
||||
|
||||
use super::{types::PeerServices, InventoryHash, Message};
|
||||
|
||||
use zebra_chain::{block, transaction};
|
||||
|
||||
use super::{
|
||||
addr::{canonical_socket_addr, ipv6_mapped_socket_addr},
|
||||
types::PeerServices,
|
||||
InventoryHash, Message,
|
||||
};
|
||||
|
||||
impl InventoryHash {
|
||||
/// Generate a proptest strategy for [`InventoryHash::Error`]s.
|
||||
pub fn error_strategy() -> BoxedStrategy<Self> {
|
||||
|
@ -104,3 +111,17 @@ impl Message {
|
|||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a random canonical Zebra `SocketAddr`.
|
||||
///
|
||||
/// See [`canonical_ip_addr`] for details.
|
||||
pub fn canonical_socket_addr_strategy() -> impl Strategy<Value = SocketAddr> {
|
||||
any::<SocketAddr>().prop_map(canonical_socket_addr)
|
||||
}
|
||||
|
||||
/// Returns a random `SocketAddrV6` for use in `addr` (v1) Zcash network messages.
|
||||
///
|
||||
/// See [`canonical_ip_addr`] for details.
|
||||
pub fn addr_v1_ipv6_mapped_socket_addr_strategy() -> impl Strategy<Value = SocketAddrV6> {
|
||||
any::<SocketAddr>().prop_map(ipv6_mapped_socket_addr)
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ use zebra_chain::{
|
|||
parameters::Network,
|
||||
serialization::{
|
||||
sha256d, zcash_deserialize_bytes_external_count, FakeWriter, ReadZcashExt,
|
||||
SerializationError as Error, WriteZcashExt, ZcashDeserialize, ZcashSerialize,
|
||||
SerializationError as Error, ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize,
|
||||
MAX_PROTOCOL_MESSAGE_LEN,
|
||||
},
|
||||
transaction::Transaction,
|
||||
|
@ -25,6 +25,7 @@ use zebra_chain::{
|
|||
use crate::constants;
|
||||
|
||||
use super::{
|
||||
addr::{AddrInVersion, AddrV1},
|
||||
message::{Message, RejectReason},
|
||||
types::*,
|
||||
};
|
||||
|
@ -212,13 +213,8 @@ impl Codec {
|
|||
// serialization can not error.
|
||||
writer.write_i64::<LittleEndian>(timestamp.timestamp())?;
|
||||
|
||||
let (recv_services, recv_addr) = address_recv;
|
||||
writer.write_u64::<LittleEndian>(recv_services.bits())?;
|
||||
writer.write_socket_addr(*recv_addr)?;
|
||||
|
||||
let (from_services, from_addr) = address_from;
|
||||
writer.write_u64::<LittleEndian>(from_services.bits())?;
|
||||
writer.write_socket_addr(*from_addr)?;
|
||||
address_recv.zcash_serialize(&mut writer)?;
|
||||
address_from.zcash_serialize(&mut writer)?;
|
||||
|
||||
writer.write_u64::<LittleEndian>(nonce.0)?;
|
||||
user_agent.zcash_serialize(&mut writer)?;
|
||||
|
@ -245,7 +241,12 @@ impl Codec {
|
|||
writer.write_all(data)?;
|
||||
}
|
||||
}
|
||||
Message::Addr(addrs) => addrs.zcash_serialize(&mut writer)?,
|
||||
Message::Addr(addrs) => {
|
||||
// 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();
|
||||
v1_addrs.zcash_serialize(&mut writer)?
|
||||
}
|
||||
Message::GetAddr => { /* Empty payload -- no-op */ }
|
||||
Message::Block(block) => block.zcash_serialize(&mut writer)?,
|
||||
Message::GetBlocks { known_blocks, stop } => {
|
||||
|
@ -455,14 +456,8 @@ impl Codec {
|
|||
.ok_or(Error::Parse(
|
||||
"version timestamp is out of range for DateTime",
|
||||
))?,
|
||||
address_recv: (
|
||||
PeerServices::from_bits_truncate(reader.read_u64::<LittleEndian>()?),
|
||||
reader.read_socket_addr()?,
|
||||
),
|
||||
address_from: (
|
||||
PeerServices::from_bits_truncate(reader.read_u64::<LittleEndian>()?),
|
||||
reader.read_socket_addr()?,
|
||||
),
|
||||
address_recv: AddrInVersion::zcash_deserialize(&mut reader)?,
|
||||
address_from: AddrInVersion::zcash_deserialize(&mut reader)?,
|
||||
nonce: Nonce(reader.read_u64::<LittleEndian>()?),
|
||||
user_agent: String::zcash_deserialize(&mut reader)?,
|
||||
start_height: block::Height(reader.read_u32::<LittleEndian>()?),
|
||||
|
@ -514,8 +509,13 @@ impl Codec {
|
|||
})
|
||||
}
|
||||
|
||||
/// Deserialize an `addr` (v1) message into a list of `MetaAddr`s.
|
||||
fn read_addr<R: Read>(&self, reader: R) -> Result<Message, Error> {
|
||||
Ok(Message::Addr(Vec::zcash_deserialize(reader)?))
|
||||
let addrs: Vec<AddrV1> = reader.zcash_deserialize_into()?;
|
||||
|
||||
// Convert the received address format to Zebra's internal `MetaAddr`.
|
||||
let addrs = addrs.into_iter().map(Into::into).collect();
|
||||
Ok(Message::Addr(addrs))
|
||||
}
|
||||
|
||||
fn read_getaddr<R: Read>(&self, mut _reader: R) -> Result<Message, Error> {
|
||||
|
@ -641,13 +641,13 @@ mod tests {
|
|||
version: crate::constants::CURRENT_NETWORK_PROTOCOL_VERSION,
|
||||
services,
|
||||
timestamp,
|
||||
address_recv: (
|
||||
services,
|
||||
address_recv: AddrInVersion::new(
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 6)), 8233),
|
||||
services,
|
||||
),
|
||||
address_from: (
|
||||
services,
|
||||
address_from: AddrInVersion::new(
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 6)), 8233),
|
||||
services,
|
||||
),
|
||||
nonce: Nonce(0x9082_4908_8927_9238),
|
||||
user_agent: "Zebra".to_owned(),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! Definitions of network messages.
|
||||
|
||||
use std::{error::Error, fmt, net, sync::Arc};
|
||||
use std::{error::Error, fmt, sync::Arc};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
|
@ -11,7 +11,7 @@ use zebra_chain::{
|
|||
|
||||
use crate::meta_addr::MetaAddr;
|
||||
|
||||
use super::{inv::InventoryHash, types::*};
|
||||
use super::{addr::AddrInVersion, inv::InventoryHash, types::*};
|
||||
|
||||
#[cfg(any(test, feature = "proptest-impl"))]
|
||||
use proptest_derive::Arbitrary;
|
||||
|
@ -65,11 +65,11 @@ pub enum Message {
|
|||
/// advertised network services.
|
||||
///
|
||||
/// Q: how does the handshake know the remote peer's services already?
|
||||
address_recv: (PeerServices, net::SocketAddr),
|
||||
address_recv: AddrInVersion,
|
||||
|
||||
/// The network address of the node sending this message, and its
|
||||
/// advertised network services.
|
||||
address_from: (PeerServices, net::SocketAddr),
|
||||
address_from: AddrInVersion,
|
||||
|
||||
/// Node random nonce, randomly generated every time a version
|
||||
/// packet is sent. This nonce is used to detect connections
|
||||
|
@ -138,9 +138,15 @@ pub enum Message {
|
|||
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#getaddr)
|
||||
GetAddr,
|
||||
|
||||
/// An `addr` message.
|
||||
/// A sent or received `addr` message, or a received `addrv2` message.
|
||||
///
|
||||
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#addr)
|
||||
/// Currently, Zebra:
|
||||
/// - sends and receives `addr` messages,
|
||||
/// - parses received `addrv2` messages,
|
||||
/// - but does not send `addrv2` messages.
|
||||
///
|
||||
/// [addr Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#addr)
|
||||
/// [addrv2 ZIP 155](https://zips.z.cash/zip-0155#specification)
|
||||
Addr(Vec<MetaAddr>),
|
||||
|
||||
/// A `getblocks` message.
|
||||
|
|
|
@ -8,7 +8,10 @@ use zebra_chain::serialization::{TrustedPreallocate, ZcashSerialize, MAX_PROTOCO
|
|||
|
||||
use crate::meta_addr::MetaAddr;
|
||||
|
||||
use super::super::{addr::META_ADDR_SIZE, inv::InventoryHash};
|
||||
use super::super::{
|
||||
addr::{AddrV1, ADDR_V1_SIZE},
|
||||
inv::InventoryHash,
|
||||
};
|
||||
|
||||
proptest! {
|
||||
/// Confirm that each InventoryHash takes the expected size in bytes when serialized.
|
||||
|
@ -63,45 +66,47 @@ proptest! {
|
|||
}
|
||||
|
||||
proptest! {
|
||||
/// Confirm that each MetaAddr takes exactly META_ADDR_SIZE bytes when serialized.
|
||||
/// Confirm that each AddrV1 takes exactly ADDR_V1_SIZE bytes when serialized.
|
||||
/// This verifies that our calculated `TrustedPreallocate::max_allocation()` is indeed an upper bound.
|
||||
#[test]
|
||||
fn meta_addr_size_is_correct(addr in MetaAddr::arbitrary()) {
|
||||
fn addr_v1_size_is_correct(addr in MetaAddr::arbitrary()) {
|
||||
zebra_test::init();
|
||||
|
||||
// We require sanitization before serialization
|
||||
let addr = addr.sanitize();
|
||||
prop_assume!(addr.is_some());
|
||||
let addr = addr.unwrap();
|
||||
|
||||
let addr: AddrV1 = addr.unwrap().into();
|
||||
|
||||
let serialized = addr
|
||||
.zcash_serialize_to_vec()
|
||||
.expect("Serialization to vec must succeed");
|
||||
assert!(serialized.len() == META_ADDR_SIZE)
|
||||
assert!(serialized.len() == ADDR_V1_SIZE)
|
||||
}
|
||||
|
||||
/// Verifies that...
|
||||
/// 1. The smallest disallowed vector of `MetaAddrs`s is too large to fit in a legal Zcash message
|
||||
/// 1. The smallest disallowed vector of `AddrV1`s is too large to fit in a legal Zcash message
|
||||
/// 2. The largest allowed vector is small enough to fit in a legal Zcash message
|
||||
#[test]
|
||||
fn meta_addr_max_allocation_is_correct(addr in MetaAddr::arbitrary()) {
|
||||
fn addr_v1_max_allocation_is_correct(addr in MetaAddr::arbitrary()) {
|
||||
zebra_test::init();
|
||||
|
||||
// We require sanitization before serialization
|
||||
let addr = addr.sanitize();
|
||||
prop_assume!(addr.is_some());
|
||||
let addr = addr.unwrap();
|
||||
|
||||
let max_allocation: usize = MetaAddr::max_allocation().try_into().unwrap();
|
||||
let addr: AddrV1 = addr.unwrap().into();
|
||||
|
||||
let max_allocation: usize = AddrV1::max_allocation().try_into().unwrap();
|
||||
let mut smallest_disallowed_vec = Vec::with_capacity(max_allocation + 1);
|
||||
for _ in 0..(MetaAddr::max_allocation() + 1) {
|
||||
for _ in 0..(AddrV1::max_allocation() + 1) {
|
||||
smallest_disallowed_vec.push(addr);
|
||||
}
|
||||
let smallest_disallowed_serialized = smallest_disallowed_vec
|
||||
.zcash_serialize_to_vec()
|
||||
.expect("Serialization to vec must succeed");
|
||||
// Check that our smallest_disallowed_vec is only one item larger than the limit
|
||||
assert!(((smallest_disallowed_vec.len() - 1) as u64) == MetaAddr::max_allocation());
|
||||
assert!(((smallest_disallowed_vec.len() - 1) as u64) == AddrV1::max_allocation());
|
||||
// Check that our smallest_disallowed_vec is too big to send in a valid Zcash message
|
||||
assert!(smallest_disallowed_serialized.len() > MAX_PROTOCOL_MESSAGE_LEN);
|
||||
|
||||
|
@ -112,8 +117,8 @@ proptest! {
|
|||
.zcash_serialize_to_vec()
|
||||
.expect("Serialization to vec must succeed");
|
||||
|
||||
// Check that our largest_allowed_vec contains the maximum number of MetaAddrs
|
||||
assert!((largest_allowed_vec.len() as u64) == MetaAddr::max_allocation());
|
||||
// Check that our largest_allowed_vec contains the maximum number of AddrV1s
|
||||
assert!((largest_allowed_vec.len() as u64) == AddrV1::max_allocation());
|
||||
// Check that our largest_allowed_vec is small enough to fit in a Zcash message.
|
||||
assert!(largest_allowed_serialized.len() <= MAX_PROTOCOL_MESSAGE_LEN);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
//! Randomised property tests for Zebra's Zcash network protocol types.
|
||||
|
||||
use bytes::BytesMut;
|
||||
use proptest::{collection::vec, prelude::*};
|
||||
use tokio_util::codec::{Decoder, Encoder};
|
||||
|
||||
use zebra_chain::serialization::{
|
||||
SerializationError, ZcashDeserializeInto, ZcashSerialize, MAX_PROTOCOL_MESSAGE_LEN,
|
||||
SerializationError, ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize,
|
||||
MAX_PROTOCOL_MESSAGE_LEN,
|
||||
};
|
||||
|
||||
use crate::{meta_addr::tests::check, meta_addr::MetaAddr, protocol::external::addr::AddrV1};
|
||||
|
||||
use super::super::{Codec, InventoryHash, Message};
|
||||
|
||||
/// Maximum number of random input bytes to try to deserialize an [`InventoryHash`] from.
|
||||
|
@ -89,4 +94,137 @@ proptest! {
|
|||
prop_assert!(decoded.is_ok());
|
||||
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>()) {
|
||||
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
|
||||
let addr_bytes = AddrV1::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 = AddrV1::zcash_deserialize(addr_bytes.as_slice());
|
||||
prop_assert!(
|
||||
deserialized_addr.is_ok(),
|
||||
"unexpected deserialization error: {:?}, addr: {:?}, bytes: {:?}",
|
||||
deserialized_addr,
|
||||
sanitized_addr,
|
||||
hex::encode(addr_bytes),
|
||||
);
|
||||
let deserialized_addr = deserialized_addr.unwrap().into();
|
||||
|
||||
// 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 = 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,
|
||||
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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue