diff --git a/bitcoin/src/network/constants.rs b/bitcoin/src/network/constants.rs index 6653e591..7795d3a5 100644 --- a/bitcoin/src/network/constants.rs +++ b/bitcoin/src/network/constants.rs @@ -26,14 +26,19 @@ //! assert_eq!(&bytes[..], &[0xF9, 0xBE, 0xB4, 0xD9]); //! ``` -use core::{fmt, ops, convert::From}; +use core::{fmt, ops, convert::TryFrom, borrow::Borrow, borrow::BorrowMut}; use core::str::FromStr; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use crate::io; +use crate::prelude::{String, ToOwned}; use crate::consensus::encode::{self, Encodable, Decodable}; +use crate::internal_macros::debug_from_display; +use crate::hashes::hex::{FromHex, Error}; +use crate::error::impl_std_error; +use bitcoin_internals::write_err; /// Version of the protocol as appearing in network message headers /// This constant is used to signal to other peers which features you support. @@ -75,20 +80,14 @@ impl Network { /// # Examples /// /// ```rust - /// use bitcoin::network::constants::Network; + /// use bitcoin::network::constants::{Network, Magic}; + /// use std::convert::TryFrom; /// - /// assert_eq!(Some(Network::Bitcoin), Network::from_magic(0xD9B4BEF9)); - /// assert_eq!(None, Network::from_magic(0xFFFFFFFF)); + /// assert_eq!(Ok(Network::Bitcoin), Network::try_from(Magic::from_bytes([0xF9, 0xBE, 0xB4, 0xD9]))); + /// assert_eq!(None, Network::from_magic(Magic::from_bytes([0xFF, 0xFF, 0xFF, 0xFF]))); /// ``` - pub fn from_magic(magic: u32) -> Option { - // Note: any new entries here must be added to `magic` below - match magic { - 0xD9B4BEF9 => Some(Network::Bitcoin), - 0x0709110B => Some(Network::Testnet), - 0x40CF030A => Some(Network::Signet), - 0xDAB5BFFA => Some(Network::Regtest), - _ => None - } + pub fn from_magic(magic: Magic) -> Option { + Network::try_from(magic).ok() } /// Return the network magic bytes, which should be encoded little-endian @@ -97,33 +96,13 @@ impl Network { /// # Examples /// /// ```rust - /// use bitcoin::network::constants::Network; + /// use bitcoin::network::constants::{Network, Magic}; /// /// let network = Network::Bitcoin; - /// assert_eq!(network.magic(), 0xD9B4BEF9); + /// assert_eq!(network.magic(), Magic::from_bytes([0xF9, 0xBE, 0xB4, 0xD9])); /// ``` - pub fn magic(self) -> u32 { - // Note: any new entries here must be added to `from_magic` above - match self { - Network::Bitcoin => 0xD9B4BEF9, - Network::Testnet => 0x0709110B, - Network::Signet => 0x40CF030A, - Network::Regtest => 0xDAB5BFFA, - } - } -} - -impl fmt::Display for Network { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - use Network::*; - - let s = match *self { - Bitcoin => "bitcoin", - Testnet => "testnet", - Signet => "signet", - Regtest => "regtest", - }; - write!(f, "{}", s) + pub fn magic(self) -> Magic { + Magic::from(self) } } @@ -150,6 +129,180 @@ impl FromStr for Network { } } +impl fmt::Display for Network { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use Network::*; + + let s = match *self { + Bitcoin => "bitcoin", + Testnet => "testnet", + Signet => "signet", + Regtest => "regtest", + }; + write!(f, "{}", s) + } +} + +/// Network magic bytes to identify the cryptocurrency network the message was intended for. +#[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)] +pub struct Magic([u8; 4]); + +impl Magic { + /// Bitcoin mainnet network magic bytes. + pub const BITCOIN: Self = Self([0xF9, 0xBE, 0xB4, 0xD9]); + /// Bitcoin testnet network magic bytes. + pub const TESTNET: Self = Self([0x0B, 0x11, 0x09, 0x07]); + /// Bitcoin signet network magic bytes. + pub const SIGNET: Self = Self([0x0A, 0x03, 0xCF, 0x40]); + /// Bitcoin regtest network magic bytes. + pub const REGTEST: Self = Self([0xFA, 0xBF, 0xB5, 0xDA]); + + /// Create network magic from bytes. + pub fn from_bytes(bytes: [u8; 4]) -> Magic { + Magic(bytes) + } + + /// Get network magic bytes. + pub fn to_bytes(self) -> [u8; 4] { + self.0 + } +} + +/// An error in parsing magic bytes. +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct ParseMagicError { + /// The error that occurred when parsing the string. + error: Error, + /// The byte string that failed to parse. + magic: String +} + +impl FromStr for Magic { + type Err = ParseMagicError; + + fn from_str(s: &str) -> Result { + match <[u8; 4]>::from_hex(s) { + Ok(magic) => Ok(Magic::from_bytes(magic)), + Err(e) => Err(ParseMagicError { error: e, magic: s.to_owned() }) + } + } +} + +impl From for Magic { + fn from(network: Network) -> Magic { + match network { + // Note: new network entries must explicitly be matched in `try_from` below. + Network::Bitcoin => Magic::BITCOIN, + Network::Testnet => Magic::TESTNET, + Network::Signet => Magic::SIGNET, + Network::Regtest => Magic::REGTEST + } + } +} + +/// Error in parsing magic from string. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct UnknownMagic(Magic); + +impl TryFrom for Network { + type Error = UnknownMagic; + + fn try_from(magic: Magic) -> Result { + match magic { + // Note: any new network entries must be matched against here. + Magic::BITCOIN => Ok(Network::Bitcoin), + Magic::TESTNET => Ok(Network::Testnet), + Magic::SIGNET => Ok(Network::Signet), + Magic::REGTEST => Ok(Network::Regtest), + _ => Err(UnknownMagic(magic)) + } + } +} + +impl fmt::Display for Magic { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + bitcoin_internals::fmt_hex_exact!(f, 4, &self.0, bitcoin_internals::hex::Case::Lower)?; + Ok(()) + } +} +debug_from_display!(Magic); + +impl fmt::LowerHex for Magic { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + bitcoin_internals::fmt_hex_exact!(f, 4, &self.0, bitcoin_internals::hex::Case::Lower)?; + Ok(()) + } +} + +impl fmt::UpperHex for Magic { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + bitcoin_internals::fmt_hex_exact!(f, 4, &self.0, bitcoin_internals::hex::Case::Upper)?; + Ok(()) + } +} + +impl Encodable for Magic { + fn consensus_encode(&self, writer: &mut W) -> Result { + self.0.consensus_encode(writer) + } +} + +impl Decodable for Magic { + fn consensus_decode(reader: &mut R) -> Result { + Ok(Magic(Decodable::consensus_decode(reader)?)) + } +} + +impl AsRef<[u8]> for Magic { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl AsRef<[u8; 4]> for Magic { + fn as_ref(&self) -> &[u8; 4] { + &self.0 + } +} + +impl Borrow<[u8]> for Magic { + fn borrow(&self) -> &[u8] { + &self.0 + } +} + +impl Borrow<[u8; 4]> for Magic { + fn borrow(&self) -> &[u8; 4] { + &self.0 + } +} + +impl BorrowMut<[u8]> for Magic { + fn borrow_mut(&mut self) -> &mut [u8] { + &mut self.0 + } +} + +impl BorrowMut<[u8; 4]> for Magic { + fn borrow_mut(&mut self) -> &mut [u8; 4] { + &mut self.0 + } +} + +impl fmt::Display for ParseMagicError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write_err!(f, "failed to parse {} as network magic", self.magic; self.error) + } +} +impl_std_error!(ParseMagicError, error); + +impl fmt::Display for UnknownMagic { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "unknown network magic {}", self.0) + } +} +impl_std_error!(UnknownMagic); + /// Flags to indicate which network services a node supports. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ServiceFlags(u64); @@ -320,8 +473,10 @@ impl Decodable for ServiceFlags { #[cfg(test)] mod tests { - use super::{Network, ServiceFlags}; + use super::{Network, ServiceFlags, Magic}; use crate::consensus::encode::{deserialize, serialize}; + use std::convert::TryFrom; + use std::str::FromStr; #[test] fn serialize_test() { @@ -408,4 +563,20 @@ mod tests { assert_eq!(back, network); } } + + #[test] + fn magic_from_str() { + let known_network_magic_strs = [ + ("f9beb4d9", Network::Bitcoin), + ("0b110907", Network::Testnet), + ("fabfb5da", Network::Regtest), + ("0a03cf40", Network::Signet), + ]; + + for (magic_str, network) in &known_network_magic_strs { + let magic: Magic = Magic::from_str(magic_str).unwrap(); + assert_eq!(Network::try_from(magic).unwrap(), *network); + assert_eq!(&magic.to_string(), magic_str); + } + } } diff --git a/bitcoin/src/network/message.rs b/bitcoin/src/network/message.rs index 627d8a2d..17234df7 100644 --- a/bitcoin/src/network/message.rs +++ b/bitcoin/src/network/message.rs @@ -21,6 +21,7 @@ use crate::network::{message_network, message_bloom}; use crate::network::message_blockdata; use crate::network::message_filter; use crate::network::message_compact_blocks; +use crate::network::constants::Magic; use crate::consensus::encode::{CheckedData, Decodable, Encodable, VarInt}; use crate::consensus::{encode, serialize}; use crate::util::merkleblock::MerkleBlock; @@ -149,7 +150,7 @@ crate::error::impl_std_error!(CommandStringError); #[derive(Clone, Debug, PartialEq, Eq)] pub struct RawNetworkMessage { /// Magic bytes to identify the network these messages are meant for - pub magic: u32, + pub magic: Magic, /// The actual message data pub payload: NetworkMessage } @@ -470,7 +471,7 @@ impl Decodable for RawNetworkMessage { mod test { use std::net::Ipv4Addr; use super::{RawNetworkMessage, NetworkMessage, CommandString}; - use crate::network::constants::ServiceFlags; + use crate::network::constants::{ServiceFlags, Magic, Network}; use crate::consensus::encode::{deserialize, deserialize_partial, serialize}; use crate::hashes::hex::FromHex; use crate::hashes::sha256d::Hash; @@ -545,7 +546,7 @@ mod test { ]; for msg in msgs { - let raw_msg = RawNetworkMessage {magic: 57, payload: msg}; + let raw_msg = RawNetworkMessage {magic: Magic::from_bytes([57, 0, 0, 0]), payload: msg}; assert_eq!(deserialize::(&serialize(&raw_msg)).unwrap(), raw_msg); } @@ -573,7 +574,7 @@ mod test { #[test] fn serialize_verack_test() { - assert_eq!(serialize(&RawNetworkMessage { magic: 0xd9b4bef9, payload: NetworkMessage::Verack }), + assert_eq!(serialize(&RawNetworkMessage { magic: Magic::from(Network::Bitcoin), payload: NetworkMessage::Verack }), vec![0xf9, 0xbe, 0xb4, 0xd9, 0x76, 0x65, 0x72, 0x61, 0x63, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d, 0xf6, 0xe0, 0xe2]); @@ -581,7 +582,7 @@ mod test { #[test] fn serialize_ping_test() { - assert_eq!(serialize(&RawNetworkMessage { magic: 0xd9b4bef9, payload: NetworkMessage::Ping(100) }), + assert_eq!(serialize(&RawNetworkMessage { magic: Magic::from(Network::Bitcoin), payload: NetworkMessage::Ping(100) }), vec![0xf9, 0xbe, 0xb4, 0xd9, 0x70, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x24, 0x67, 0xf1, 0x1d, @@ -591,7 +592,7 @@ mod test { #[test] fn serialize_mempool_test() { - assert_eq!(serialize(&RawNetworkMessage { magic: 0xd9b4bef9, payload: NetworkMessage::MemPool }), + assert_eq!(serialize(&RawNetworkMessage { magic: Magic::from(Network::Bitcoin), payload: NetworkMessage::MemPool }), vec![0xf9, 0xbe, 0xb4, 0xd9, 0x6d, 0x65, 0x6d, 0x70, 0x6f, 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d, 0xf6, 0xe0, 0xe2]); @@ -599,7 +600,7 @@ mod test { #[test] fn serialize_getaddr_test() { - assert_eq!(serialize(&RawNetworkMessage { magic: 0xd9b4bef9, payload: NetworkMessage::GetAddr }), + assert_eq!(serialize(&RawNetworkMessage { magic: Magic::from(Network::Bitcoin), payload: NetworkMessage::GetAddr }), vec![0xf9, 0xbe, 0xb4, 0xd9, 0x67, 0x65, 0x74, 0x61, 0x64, 0x64, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d, 0xf6, 0xe0, 0xe2]); @@ -611,7 +612,7 @@ mod test { &[0xf9, 0xbe, 0xb4, 0xd9, 0x67, 0x65, 0x74, 0x61, 0x64, 0x64, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d, 0xf6, 0xe0, 0xe2]); - let preimage = RawNetworkMessage { magic: 0xd9b4bef9, payload: NetworkMessage::GetAddr }; + let preimage = RawNetworkMessage { magic: Magic::from(Network::Bitcoin), payload: NetworkMessage::GetAddr }; assert!(msg.is_ok()); let msg : RawNetworkMessage = msg.unwrap(); assert_eq!(preimage.magic, msg.magic); @@ -640,7 +641,7 @@ mod test { assert!(msg.is_ok()); let msg = msg.unwrap(); - assert_eq!(msg.magic, 0xd9b4bef9); + assert_eq!(msg.magic, Magic::from(Network::Bitcoin)); if let NetworkMessage::Version(version_msg) = msg.payload { assert_eq!(version_msg.version, 70015); assert_eq!(version_msg.services, ServiceFlags::NETWORK | ServiceFlags::BLOOM | ServiceFlags::WITNESS | ServiceFlags::NETWORK_LIMITED); @@ -677,7 +678,7 @@ mod test { let (msg, consumed) = msg.unwrap(); assert_eq!(consumed, data.to_vec().len() - 2); - assert_eq!(msg.magic, 0xd9b4bef9); + assert_eq!(msg.magic, Magic::from(Network::Bitcoin)); if let NetworkMessage::Version(version_msg) = msg.payload { assert_eq!(version_msg.version, 70015); assert_eq!(version_msg.services, ServiceFlags::NETWORK | ServiceFlags::BLOOM | ServiceFlags::WITNESS | ServiceFlags::NETWORK_LIMITED);