From ad29084582faf0633818eccd4d95aaab25b893fc Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 20 Sep 2022 15:19:47 +1000 Subject: [PATCH 1/2] Add Network serde roundtrip test In preparation for patching the `Network` serde impls; add a roundtrip test for `serde` (de)serializing the `Network` type. --- bitcoin/src/network/constants.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/bitcoin/src/network/constants.rs b/bitcoin/src/network/constants.rs index 54704a31..50ecad35 100644 --- a/bitcoin/src/network/constants.rs +++ b/bitcoin/src/network/constants.rs @@ -348,4 +348,22 @@ mod tests { let flag = ServiceFlags::WITNESS | 0xf0.into(); assert_eq!("ServiceFlags(WITNESS|COMPACT_FILTERS|0xb0)", flag.to_string()); } + + #[test] + #[cfg(feature = "serde")] + fn serde_roundtrip() { + use Network::*; + let tests = vec![(Bitcoin, "bitcoin"), (Testnet, "testnet"), (Signet, "signet"), (Regtest, "regtest")]; + + for tc in tests { + let network = tc.0; + + let want = format!("\"{}\"", tc.1); + let got = serde_json::to_string(&tc.0).expect("failed to serialize network"); + assert_eq!(got, want); + + let back: Network = serde_json::from_str(&got).expect("failed to deserialize network"); + assert_eq!(back, network); + } + } } From f429c22599cbcf61c34cbd1bc673a28fb5d41f61 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 20 Sep 2022 15:27:25 +1000 Subject: [PATCH 2/2] Remove user_enum macro The `user_enum` macro is only used a single time. The macro includes custom serde logic which can be trivially derived instead. Remove the `user_enum` macro and just implement `Network` the old fashioned way. Doing so simplifies the code. --- bitcoin/src/internal_macros.rs | 88 -------------------------------- bitcoin/src/network/constants.rs | 70 ++++++++++++++++++++----- 2 files changed, 56 insertions(+), 102 deletions(-) diff --git a/bitcoin/src/internal_macros.rs b/bitcoin/src/internal_macros.rs index bc196c6c..70dc6397 100644 --- a/bitcoin/src/internal_macros.rs +++ b/bitcoin/src/internal_macros.rs @@ -538,94 +538,6 @@ macro_rules! impl_bytes_newtype { } pub(crate) use impl_bytes_newtype; -macro_rules! user_enum { - ( - $(#[$attr:meta])* - pub enum $name:ident { - $(#[$doc:meta] - $elem:ident <-> $txt:literal),* - } - ) => ( - $(#[$attr])* - pub enum $name { - $(#[$doc] $elem),* - } - - impl core::fmt::Display for $name { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - f.pad(match *self { - $($name::$elem => $txt),* - }) - } - } - - impl core::str::FromStr for $name { - type Err = $crate::io::Error; - #[inline] - fn from_str(s: &str) -> Result { - match s { - $($txt => Ok($name::$elem)),*, - _ => { - #[cfg(not(feature = "std"))] let message = "Unknown network"; - #[cfg(feature = "std")] let message = format!("Unknown network (type {})", s); - Err($crate::io::Error::new( - $crate::io::ErrorKind::InvalidInput, - message, - )) - } - } - } - } - - #[cfg(feature = "serde")] - #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] - impl<'de> $crate::serde::Deserialize<'de> for $name { - #[inline] - fn deserialize(deserializer: D) -> Result - where - D: $crate::serde::Deserializer<'de>, - { - use core::fmt::{self, Formatter}; - - struct Visitor; - impl<'de> $crate::serde::de::Visitor<'de> for Visitor { - type Value = $name; - - fn expecting(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("an enum value") - } - - fn visit_str(self, v: &str) -> Result - where - E: $crate::serde::de::Error, - { - static FIELDS: &'static [&'static str] = &[$(stringify!($txt)),*]; - - $( if v == $txt { Ok($name::$elem) } )else* - else { - Err(E::unknown_variant(v, FIELDS)) - } - } - } - - deserializer.deserialize_str(Visitor) - } - } - - #[cfg(feature = "serde")] - #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] - impl $crate::serde::Serialize for $name { - fn serialize(&self, serializer: S) -> Result - where - S: $crate::serde::Serializer, - { - serializer.collect_str(&self) - } - } - ); -} -pub(crate) use user_enum; - /// Asserts a boolean expression at compile time. macro_rules! const_assert { ($x:expr) => {{ diff --git a/bitcoin/src/network/constants.rs b/bitcoin/src/network/constants.rs index 50ecad35..6653e591 100644 --- a/bitcoin/src/network/constants.rs +++ b/bitcoin/src/network/constants.rs @@ -27,10 +27,13 @@ //! ``` use core::{fmt, ops, convert::From}; +use core::str::FromStr; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; use crate::io; use crate::consensus::encode::{self, Encodable, Decodable}; -use crate::internal_macros::user_enum; /// Version of the protocol as appearing in network message headers /// This constant is used to signal to other peers which features you support. @@ -49,19 +52,21 @@ use crate::internal_macros::user_enum; /// 60001 - Support `pong` message and nonce in `ping` message pub const PROTOCOL_VERSION: u32 = 70001; -user_enum! { - /// The cryptocurrency to act on - #[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Debug)] - pub enum Network { - /// Classic Bitcoin - Bitcoin <-> "bitcoin", - /// Bitcoin's testnet - Testnet <-> "testnet", - /// Bitcoin's signet - Signet <-> "signet", - /// Bitcoin's regtest - Regtest <-> "regtest" - } +/// The cryptocurrency network to act on. +#[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] +#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))] +#[non_exhaustive] +pub enum Network { + /// Mainnet Bitcoin. + Bitcoin, + /// Bitcoin's testnet network. + Testnet, + /// Bitcoin's signet network. + Signet, + /// Bitcoin's regtest network. + Regtest, } impl Network { @@ -108,6 +113,43 @@ impl 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) + } +} + +impl FromStr for Network { + type Err = io::Error; + #[inline] + fn from_str(s: &str) -> Result { + use Network::*; + + let network = match s { + "bitcoin" => Bitcoin, + "testnet" => Testnet, + "signet" => Signet, + "regtest" => Regtest, + _ => { + #[cfg(feature = "std")] + let message = format!("Unknown network (type {})", s); + #[cfg(not(feature = "std"))] + let message = "Unknown network"; + return Err(io::Error::new(io::ErrorKind::InvalidInput, message)); + } + }; + Ok(network) + } +} + /// Flags to indicate which network services a node supports. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ServiceFlags(u64);