diff --git a/src/network/address.rs b/src/network/address.rs index ff841244..0e8f4040 100644 --- a/src/network/address.rs +++ b/src/network/address.rs @@ -22,12 +22,14 @@ use std::io; use std::fmt; use std::net::{SocketAddr, Ipv6Addr, SocketAddrV4, SocketAddrV6}; +use network::constants::ServiceFlags; use consensus::encode::{self, Decodable, Encodable}; /// A message which can be sent on the Bitcoin network +#[derive(Clone, PartialEq, Eq, Hash)] pub struct Address { /// Services provided by the peer whose address this is - pub services: u64, + pub services: ServiceFlags, /// Network byte-order ipv6 address, or ipv4-mapped ipv6 address pub address: [u16; 8], /// Network port @@ -38,7 +40,7 @@ const ONION : [u16; 3] = [0xFD87, 0xD87E, 0xEB43]; impl Address { /// Create an address message for a socket - pub fn new (socket :&SocketAddr, services: u64) -> Address { + pub fn new (socket :&SocketAddr, services: ServiceFlags) -> Address { let (address, port) = match socket { &SocketAddr::V4(ref addr) => (addr.ip().to_ipv6_mapped().segments(), addr.port()), &SocketAddr::V6(ref addr) => (addr.ip().segments(), addr.port()) @@ -103,26 +105,6 @@ impl fmt::Debug for Address { } } -impl Clone for Address { - fn clone(&self) -> Address { - Address { - services: self.services, - address: self.address, - port: self.port, - } - } -} - -impl PartialEq for Address { - fn eq(&self, other: &Address) -> bool { - self.services == other.services && - &self.address[..] == &other.address[..] && - self.port == other.port - } -} - -impl Eq for Address {} - #[cfg(test)] mod test { use std::str::FromStr; @@ -134,7 +116,7 @@ mod test { #[test] fn serialize_address_test() { assert_eq!(serialize(&Address { - services: 1, + services: 1.into(), address: [0, 0, 0, 0, 0, 0xffff, 0x0a00, 0x0001], port: 8333 }), @@ -154,7 +136,7 @@ mod test { _ => false } ); - assert_eq!(full.services, 1); + assert_eq!(full.services, 1.into()); assert_eq!(full.address, [0, 0, 0, 0, 0, 0xffff, 0x0a00, 0x0001]); assert_eq!(full.port, 8333); @@ -166,11 +148,11 @@ mod test { #[test] fn test_socket_addr () { let s4 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(111,222,123,4)), 5555); - let a4 = Address::new(&s4, 9); + let a4 = Address::new(&s4, 9.into()); assert_eq!(a4.socket_addr().unwrap(), s4); let s6 = SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777, 0x8888)), 9999); - let a6 = Address::new(&s6, 9); + let a6 = Address::new(&s6, 9.into()); assert_eq!(a6.socket_addr().unwrap(), s6); } @@ -179,7 +161,7 @@ mod test { let onionaddr = SocketAddr::new( IpAddr::V6( Ipv6Addr::from_str("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").unwrap()), 1111); - let addr = Address::new(&onionaddr, 0); + let addr = Address::new(&onionaddr, 0.into()); assert!(addr.socket_addr().is_err()); } } diff --git a/src/network/constants.rs b/src/network/constants.rs index bd7b523b..37ba835c 100644 --- a/src/network/constants.rs +++ b/src/network/constants.rs @@ -37,12 +37,12 @@ //! assert_eq!(&bytes[..], &[0xF9, 0xBE, 0xB4, 0xD9]); //! ``` +use std::{fmt, io, ops}; + +use consensus::encode::{self, Encodable, Decodable}; + /// Version of the protocol as appearing in network message headers pub const PROTOCOL_VERSION: u32 = 70001; -/// Bitfield of services provided by this node -pub const SERVICES: u64 = 0; -/// User agent as it appears in the version message -pub const USER_AGENT: &'static str = "bitcoin-rust v0.1"; user_enum! { /// The cryptocurrency to act on @@ -99,9 +99,176 @@ impl Network { } } +/// Flags to indicate which network services a node supports. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ServiceFlags(u64); + +impl ServiceFlags { + /// NONE means no services supported. + pub const NONE: ServiceFlags = ServiceFlags(0); + + /// NETWORK means that the node is capable of serving the complete block chain. It is currently + /// set by all Bitcoin Core non pruned nodes, and is unset by SPV clients or other light + /// clients. + pub const NETWORK: ServiceFlags = ServiceFlags(1 << 0); + + /// GETUTXO means the node is capable of responding to the getutxo protocol request. Bitcoin + /// Core does not support this but a patch set called Bitcoin XT does. + /// See BIP 64 for details on how this is implemented. + pub const GETUTXO: ServiceFlags = ServiceFlags(1 << 1); + + /// BLOOM means the node is capable and willing to handle bloom-filtered connections. Bitcoin + /// Core nodes used to support this by default, without advertising this bit, but no longer do + /// as of protocol version 70011 (= NO_BLOOM_VERSION) + pub const BLOOM: ServiceFlags = ServiceFlags(1 << 2); + + /// WITNESS indicates that a node can be asked for blocks and transactions including witness + /// data. + pub const WITNESS: ServiceFlags = ServiceFlags(1 << 3); + + /// NETWORK_LIMITED means the same as NODE_NETWORK with the limitation of only serving the last + /// 288 (2 day) blocks. + /// See BIP159 for details on how this is implemented. + pub const NETWORK_LIMITED: ServiceFlags = ServiceFlags(1 << 10); + + // NOTE: When adding new flags, remember to update the Display impl accordingly. + + /// Add [ServiceFlags] together. + /// + /// Returns itself. + pub fn add(&mut self, other: ServiceFlags) -> ServiceFlags { + self.0 |= other.0; + *self + } + + /// Remove [ServiceFlags] from this. + /// + /// Returns itself. + pub fn remove(&mut self, other: ServiceFlags) -> ServiceFlags { + self.0 ^= other.0; + *self + } + + /// Check whether [ServiceFlags] are included in this one. + pub fn has(&self, flags: ServiceFlags) -> bool { + (self.0 | flags.0) == self.0 + } + + /// Get the integer representation of this [ServiceFlags]. + pub fn as_u64(&self) -> u64 { + self.0 + } +} + +impl fmt::LowerHex for ServiceFlags { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::LowerHex::fmt(&self.0, f) + } +} + +impl fmt::UpperHex for ServiceFlags { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::UpperHex::fmt(&self.0, f) + } +} + +impl fmt::Display for ServiceFlags { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if *self == ServiceFlags::NONE { + return write!(f, "ServiceFlags(NONE)"); + } + + let mut flags = self.clone(); + let mut first = true; + macro_rules! write_flag { + ($f:ident) => { + if flags.has(ServiceFlags::$f) { + if !first { + write!(f, "|")?; + } + first = false; + write!(f, stringify!($f))?; + flags.remove(ServiceFlags::$f); + } + } + } + write!(f, "ServiceFlags(")?; + write_flag!(NETWORK); + write_flag!(GETUTXO); + write_flag!(BLOOM); + write_flag!(WITNESS); + write_flag!(NETWORK_LIMITED); + // If there are unknown flags left, we append them in hex. + if flags != ServiceFlags::NONE { + if !first { + write!(f, "|")?; + } + write!(f, "0x{:x}", flags)?; + } + write!(f, ")") + } +} + +impl From for ServiceFlags { + fn from(f: u64) -> Self { + ServiceFlags(f) + } +} + +impl Into for ServiceFlags { + fn into(self) -> u64 { + self.0 + } +} + +impl ops::BitOr for ServiceFlags { + type Output = Self; + + fn bitor(mut self, rhs: Self) -> Self { + self.add(rhs) + } +} + +impl ops::BitOrAssign for ServiceFlags { + fn bitor_assign(&mut self, rhs: Self) { + self.add(rhs); + } +} + +impl ops::BitXor for ServiceFlags { + type Output = Self; + + fn bitxor(mut self, rhs: Self) -> Self { + self.remove(rhs) + } +} + +impl ops::BitXorAssign for ServiceFlags { + fn bitxor_assign(&mut self, rhs: Self) { + self.remove(rhs); + } +} + +impl Encodable for ServiceFlags { + #[inline] + fn consensus_encode( + &self, + mut s: S, + ) -> Result { + self.0.consensus_encode(&mut s) + } +} + +impl Decodable for ServiceFlags { + #[inline] + fn consensus_decode(mut d: D) -> Result { + Ok(ServiceFlags(Decodable::consensus_decode(&mut d)?)) + } +} + #[cfg(test)] mod tests { - use super::Network; + use super::{Network, ServiceFlags}; use consensus::encode::{deserialize, serialize}; #[test] @@ -133,16 +300,51 @@ mod tests { ); } - #[test] - fn string_test() { - assert_eq!(Network::Bitcoin.to_string(), "bitcoin"); - assert_eq!(Network::Testnet.to_string(), "testnet"); - assert_eq!(Network::Regtest.to_string(), "regtest"); + #[test] + fn string_test() { + assert_eq!(Network::Bitcoin.to_string(), "bitcoin"); + assert_eq!(Network::Testnet.to_string(), "testnet"); + assert_eq!(Network::Regtest.to_string(), "regtest"); - assert_eq!("bitcoin".parse::().unwrap(), Network::Bitcoin); - assert_eq!("testnet".parse::().unwrap(), Network::Testnet); - assert_eq!("regtest".parse::().unwrap(), Network::Regtest); - assert!("fakenet".parse::().is_err()); - } + assert_eq!("bitcoin".parse::().unwrap(), Network::Bitcoin); + assert_eq!("testnet".parse::().unwrap(), Network::Testnet); + assert_eq!("regtest".parse::().unwrap(), Network::Regtest); + assert!("fakenet".parse::().is_err()); + } + + #[test] + fn service_flags_test() { + let all = [ + ServiceFlags::NETWORK, + ServiceFlags::GETUTXO, + ServiceFlags::BLOOM, + ServiceFlags::WITNESS, + ServiceFlags::NETWORK_LIMITED, + ]; + + let mut flags = ServiceFlags::NONE; + for f in all.iter() { + assert!(!flags.has(*f)); + } + + flags |= ServiceFlags::WITNESS; + assert_eq!(flags, ServiceFlags::WITNESS); + + let mut flags2 = flags | ServiceFlags::GETUTXO; + for f in all.iter() { + assert_eq!(flags2.has(*f), *f == ServiceFlags::WITNESS || *f == ServiceFlags::GETUTXO); + } + + flags2 ^= ServiceFlags::WITNESS; + assert_eq!(flags2, ServiceFlags::GETUTXO); + + // Test formatting. + assert_eq!("ServiceFlags(NONE)", ServiceFlags::NONE.to_string()); + assert_eq!("ServiceFlags(WITNESS)", ServiceFlags::WITNESS.to_string()); + let flag = ServiceFlags::WITNESS | ServiceFlags::BLOOM | ServiceFlags::NETWORK; + assert_eq!("ServiceFlags(NETWORK|BLOOM|WITNESS)", flag.to_string()); + let flag = ServiceFlags::WITNESS | 0xf0.into(); + assert_eq!("ServiceFlags(WITNESS|0xf0)", flag.to_string()); + } } diff --git a/src/network/message.rs b/src/network/message.rs index 2e25206d..d87a6d4c 100644 --- a/src/network/message.rs +++ b/src/network/message.rs @@ -383,7 +383,7 @@ mod test { assert_eq!(msg.magic, 0xd9b4bef9); if let NetworkMessage::Version(version_msg) = msg.payload { assert_eq!(version_msg.version, 70015); - assert_eq!(version_msg.services, 1037); + assert_eq!(version_msg.services, 1037.into()); assert_eq!(version_msg.timestamp, 1548554224); assert_eq!(version_msg.nonce, 13952548347456104954); assert_eq!(version_msg.user_agent, "/Satoshi:0.17.1/"); @@ -420,7 +420,7 @@ mod test { assert_eq!(msg.magic, 0xd9b4bef9); if let NetworkMessage::Version(version_msg) = msg.payload { assert_eq!(version_msg.version, 70015); - assert_eq!(version_msg.services, 1037); + assert_eq!(version_msg.services, 1037.into()); assert_eq!(version_msg.timestamp, 1548554224); assert_eq!(version_msg.nonce, 13952548347456104954); assert_eq!(version_msg.user_agent, "/Satoshi:0.17.1/"); diff --git a/src/network/message_network.rs b/src/network/message_network.rs index ec8b80d7..45056507 100644 --- a/src/network/message_network.rs +++ b/src/network/message_network.rs @@ -19,7 +19,7 @@ //! use network::address::Address; -use network::constants; +use network::constants::{self, ServiceFlags}; use consensus::{Encodable, Decodable, ReadExt}; use consensus::encode; use std::io; @@ -34,7 +34,7 @@ pub struct VersionMessage { /// The P2P network protocol version pub version: u32, /// A bitmask describing the services supported by this node - pub services: u64, + pub services: ServiceFlags, /// The time at which the `version` message was sent pub timestamp: i64, /// The network address of the peer receiving the message @@ -57,7 +57,7 @@ impl VersionMessage { // TODO: we have fixed services and relay to 0 /// Constructs a new `version` message pub fn new( - services: u64, + services: ServiceFlags, timestamp: i64, receiver: Address, sender: Address, @@ -159,7 +159,7 @@ mod tests { assert!(decode.is_ok()); let real_decode = decode.unwrap(); assert_eq!(real_decode.version, 70002); - assert_eq!(real_decode.services, 1); + assert_eq!(real_decode.services, 1.into()); assert_eq!(real_decode.timestamp, 1401217254); // address decodes should be covered by Address tests assert_eq!(real_decode.nonce, 16735069437859780935); diff --git a/src/network/mod.rs b/src/network/mod.rs index d8fd9539..4ba6d16a 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -25,6 +25,7 @@ use std::error; pub mod constants; pub mod address; +pub use self::address::Address; pub mod message; pub mod message_blockdata; pub mod message_network; diff --git a/src/network/stream_reader.rs b/src/network/stream_reader.rs index 063a5ac6..7e6a1eed 100644 --- a/src/network/stream_reader.rs +++ b/src/network/stream_reader.rs @@ -156,7 +156,7 @@ mod test { assert_eq!(msg.magic, 0xd9b4bef9); if let NetworkMessage::Version(ref version_msg) = msg.payload { assert_eq!(version_msg.version, 70015); - assert_eq!(version_msg.services, 1037); + assert_eq!(version_msg.services, 1037.into()); assert_eq!(version_msg.timestamp, 1548554224); assert_eq!(version_msg.nonce, 13952548347456104954); assert_eq!(version_msg.user_agent, "/Satoshi:0.17.1/");