diff --git a/bitcoin/examples/handshake.rs b/bitcoin/examples/handshake.rs index b3f98d18..9a3bb462 100644 --- a/bitcoin/examples/handshake.rs +++ b/bitcoin/examples/handshake.rs @@ -6,7 +6,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use std::{env, process}; use bitcoin::consensus::{encode, Decodable}; -use bitcoin::network::{address, constants, message, message_network}; +use bitcoin::p2p::{self, address, message, message_network}; use bitcoin::secp256k1; use bitcoin::secp256k1::rand::Rng; @@ -29,7 +29,7 @@ fn main() { let version_message = build_version_message(address); let first_message = - message::RawNetworkMessage::new(constants::Network::Bitcoin.magic(), version_message); + message::RawNetworkMessage::new(bitcoin::Network::Bitcoin.magic(), version_message); if let Ok(mut stream) = TcpStream::connect(address) { // Send the message @@ -47,7 +47,7 @@ fn main() { println!("Received version message: {:?}", reply.payload()); let second_message = message::RawNetworkMessage::new( - constants::Network::Bitcoin.magic(), + bitcoin::Network::Bitcoin.magic(), message::NetworkMessage::Verack, ); @@ -75,16 +75,16 @@ fn build_version_message(address: SocketAddr) -> message::NetworkMessage { let my_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0); // "bitfield of features to be enabled for this connection" - let services = constants::ServiceFlags::NONE; + let services = p2p::ServiceFlags::NONE; // "standard UNIX timestamp in seconds" let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time error").as_secs(); // "The network address of the node receiving this message" - let addr_recv = address::Address::new(&address, constants::ServiceFlags::NONE); + let addr_recv = address::Address::new(&address, p2p::ServiceFlags::NONE); // "The network address of the node emitting this message" - let addr_from = address::Address::new(&my_address, constants::ServiceFlags::NONE); + let addr_from = address::Address::new(&my_address, p2p::ServiceFlags::NONE); // "Node random nonce, randomly generated every time a version packet is sent. This nonce is used to detect connections to self." let nonce: u64 = secp256k1::rand::thread_rng().gen(); diff --git a/bitcoin/src/address.rs b/bitcoin/src/address.rs index afa42dea..ced80341 100644 --- a/bitcoin/src/address.rs +++ b/bitcoin/src/address.rs @@ -46,7 +46,7 @@ use crate::blockdata::script::witness_version::{self, WitnessVersion}; use crate::blockdata::script::{self, Script, ScriptBuf}; use crate::crypto::key::{PublicKey, TapTweak, TweakedPublicKey, UntweakedPublicKey}; use crate::hash_types::{PubkeyHash, ScriptHash}; -use crate::network::constants::Network; +use crate::network::Network; use crate::prelude::*; use crate::taproot::TapNodeHash; @@ -996,7 +996,7 @@ mod tests { use super::*; use crate::crypto::key::PublicKey; - use crate::network::constants::Network::{Bitcoin, Testnet}; + use crate::network::Network::{Bitcoin, Testnet}; fn roundtrips(addr: &Address) { assert_eq!( diff --git a/bitcoin/src/bip32.rs b/bitcoin/src/bip32.rs index a1513a72..78be0174 100644 --- a/bitcoin/src/bip32.rs +++ b/bitcoin/src/bip32.rs @@ -23,7 +23,7 @@ use crate::crypto::key::{self, KeyPair, PrivateKey, PublicKey}; use crate::hash_types::XpubIdentifier; use crate::internal_macros::impl_bytes_newtype; use crate::io::Write; -use crate::network::constants::Network; +use crate::network::Network; use crate::prelude::*; /// A chain code @@ -866,7 +866,7 @@ mod tests { use super::ChildNumber::{Hardened, Normal}; use super::*; use crate::internal_macros::hex; - use crate::network::constants::Network::{self, Bitcoin}; + use crate::network::Network::{self, Bitcoin}; #[test] fn test_parse_derivation_path() { diff --git a/bitcoin/src/blockdata/constants.rs b/bitcoin/src/blockdata/constants.rs index 8a368d45..c97a5692 100644 --- a/bitcoin/src/blockdata/constants.rs +++ b/bitcoin/src/blockdata/constants.rs @@ -20,7 +20,7 @@ use crate::blockdata::script; use crate::blockdata::transaction::{OutPoint, Sequence, Transaction, TxIn, TxOut}; use crate::blockdata::witness::Witness; use crate::internal_macros::impl_bytes_newtype; -use crate::network::constants::Network; +use crate::network::Network; use crate::pow::CompactTarget; use crate::Amount; @@ -198,7 +198,7 @@ mod test { use crate::blockdata::locktime::absolute; use crate::consensus::encode::serialize; use crate::internal_macros::hex; - use crate::network::constants::Network; + use crate::network::Network; #[test] fn bitcoin_genesis_first_transaction() { diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs index edd0c223..ce782d14 100644 --- a/bitcoin/src/blockdata/transaction.rs +++ b/bitcoin/src/blockdata/transaction.rs @@ -68,7 +68,7 @@ impl OutPoint { /// /// ```rust /// use bitcoin::constants::genesis_block; - /// use bitcoin::network::constants::Network; + /// use bitcoin::Network; /// /// let block = genesis_block(Network::Bitcoin); /// let tx = &block.txdata[0]; @@ -1508,7 +1508,7 @@ mod tests { #[test] fn test_is_coinbase() { use crate::blockdata::constants; - use crate::network::constants::Network; + use crate::network::Network; let genesis = constants::genesis_block(Network::Bitcoin); assert!(genesis.txdata[0].is_coinbase()); diff --git a/bitcoin/src/consensus/encode.rs b/bitcoin/src/consensus/encode.rs index 7f5ab21e..297d1c08 100644 --- a/bitcoin/src/consensus/encode.rs +++ b/bitcoin/src/consensus/encode.rs @@ -26,7 +26,7 @@ use crate::blockdata::transaction::{Transaction, TxIn, TxOut}; use crate::hash_types::{BlockHash, FilterHash, FilterHeader, TxMerkleNode}; use crate::io::{self, Cursor, Read}; #[cfg(feature = "std")] -use crate::network::{ +use crate::p2p::{ address::{AddrV2Message, Address}, message_blockdata::Inventory, }; @@ -836,7 +836,7 @@ mod tests { use super::*; use crate::consensus::{deserialize_partial, Decodable, Encodable}; #[cfg(feature = "std")] - use crate::network::{message_blockdata::Inventory, Address}; + use crate::p2p::{message_blockdata::Inventory, Address}; #[test] fn serialize_int_test() { diff --git a/bitcoin/src/consensus/params.rs b/bitcoin/src/consensus/params.rs index cd9cb5bc..12eb1f5e 100644 --- a/bitcoin/src/consensus/params.rs +++ b/bitcoin/src/consensus/params.rs @@ -6,7 +6,7 @@ //! chains (such as mainnet, testnet). //! -use crate::network::constants::Network; +use crate::network::Network; use crate::pow::Work; /// Parameters that influence chain consensus. diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs index e870592c..fcfe14e7 100644 --- a/bitcoin/src/crypto/key.rs +++ b/bitcoin/src/crypto/key.rs @@ -18,7 +18,7 @@ pub use secp256k1::{self, constants, KeyPair, Parity, Secp256k1, Verification, X use crate::crypto::ecdsa; use crate::hash_types::{PubkeyHash, WPubkeyHash}; -use crate::network::constants::Network; +use crate::network::Network; use crate::prelude::*; use crate::taproot::{TapNodeHash, TapTweakHash}; use crate::{base58, io}; @@ -741,7 +741,7 @@ mod tests { use super::*; use crate::address::Address; use crate::io; - use crate::network::constants::Network::{Bitcoin, Testnet}; + use crate::network::Network::{Bitcoin, Testnet}; #[test] fn test_key_derivation() { diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs index ec4a2ad7..fcefe767 100644 --- a/bitcoin/src/crypto/sighash.rs +++ b/bitcoin/src/crypto/sighash.rs @@ -1120,7 +1120,7 @@ mod tests { use crate::crypto::key::PublicKey; use crate::crypto::sighash::{LegacySighash, TapSighash}; use crate::internal_macros::hex; - use crate::network::constants::Network; + use crate::network::Network; use crate::taproot::TapLeafHash; extern crate serde_json; diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs index af6e9ed2..0665662a 100644 --- a/bitcoin/src/lib.rs +++ b/bitcoin/src/lib.rs @@ -92,7 +92,7 @@ mod parse; mod serde_utils; #[macro_use] -pub mod network; +pub mod p2p; pub mod address; pub mod amount; pub mod base58; @@ -106,6 +106,7 @@ pub(crate) mod crypto; pub mod error; pub mod hash_types; pub mod merkle_tree; +pub mod network; pub mod policy; pub mod pow; pub mod psbt; @@ -146,7 +147,7 @@ pub use crate::hash_types::{ BlockHash, PubkeyHash, ScriptHash, Txid, WPubkeyHash, WScriptHash, Wtxid, }; pub use crate::merkle_tree::MerkleBlock; -pub use crate::network::constants::Network; +pub use crate::network::Network; pub use crate::pow::{CompactTarget, Target, Work}; pub use crate::psbt::Psbt; @@ -172,13 +173,13 @@ mod io_extras { #[rustfmt::skip] mod prelude { #[cfg(all(not(feature = "std"), not(test)))] - pub use alloc::{string::{String, ToString}, vec::Vec, boxed::Box, borrow::{Borrow, Cow, ToOwned}, slice, rc}; + pub use alloc::{string::{String, ToString}, vec::Vec, boxed::Box, borrow::{Borrow, BorrowMut, Cow, ToOwned}, slice, rc}; #[cfg(all(not(feature = "std"), not(test), any(not(rust_v_1_60), target_has_atomic = "ptr")))] pub use alloc::sync; #[cfg(any(feature = "std", test))] - pub use std::{string::{String, ToString}, vec::Vec, boxed::Box, borrow::{Borrow, Cow, ToOwned}, slice, rc, sync}; + pub use std::{string::{String, ToString}, vec::Vec, boxed::Box, borrow::{Borrow, BorrowMut, Cow, ToOwned}, slice, rc, sync}; #[cfg(all(not(feature = "std"), not(test)))] pub use alloc::collections::{BTreeMap, BTreeSet, btree_map, BinaryHeap}; diff --git a/bitcoin/src/network.rs b/bitcoin/src/network.rs new file mode 100644 index 00000000..9e128d4e --- /dev/null +++ b/bitcoin/src/network.rs @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Bitcoin network. +//! +//! The term "network" is overloaded, here [`Network`] refers to the specific +//! Bitcoin network we are operating on e.g., signet, regtest. The terms +//! "network" and "chain" are often used interchangeably for this concept. +//! +//! # Example: encoding a network's magic bytes +//! +//! ```rust +//! use bitcoin::Network; +//! use bitcoin::consensus::encode::serialize; +//! +//! let network = Network::Bitcoin; +//! let bytes = serialize(&network.magic()); +//! +//! assert_eq!(&bytes[..], &[0xF9, 0xBE, 0xB4, 0xD9]); +//! ``` + +use core::convert::TryFrom; +use core::fmt; +use core::fmt::Display; +use core::str::FromStr; + +use internals::write_err; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use crate::constants::ChainHash; +use crate::error::impl_std_error; +use crate::p2p::Magic; +use crate::prelude::{String, ToOwned}; + +/// 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 { + /// Creates a `Network` from the magic bytes. + /// + /// # Examples + /// + /// ```rust + /// use bitcoin::p2p::Magic; + /// use bitcoin::Network; + /// use std::convert::TryFrom; + /// + /// 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: Magic) -> Option { Network::try_from(magic).ok() } + + /// Return the network magic bytes, which should be encoded little-endian + /// at the start of every message + /// + /// # Examples + /// + /// ```rust + /// use bitcoin::p2p::Magic; + /// use bitcoin::Network; + /// + /// let network = Network::Bitcoin; + /// assert_eq!(network.magic(), Magic::from_bytes([0xF9, 0xBE, 0xB4, 0xD9])); + /// ``` + pub fn magic(self) -> Magic { Magic::from(self) } + + /// Converts a `Network` to its equivalent `bitcoind -chain` argument name. + /// + /// ```bash + /// $ bitcoin-23.0/bin/bitcoind --help | grep -C 3 '\-chain=' + /// Chain selection options: + /// + /// -chain= + /// Use the chain (default: main). Allowed values: main, test, signet, regtest + /// ``` + pub fn to_core_arg(self) -> &'static str { + match self { + Network::Bitcoin => "main", + Network::Testnet => "test", + Network::Signet => "signet", + Network::Regtest => "regtest", + } + } + + /// Converts a `bitcoind -chain` argument name to its equivalent `Network`. + /// + /// ```bash + /// $ bitcoin-23.0/bin/bitcoind --help | grep -C 3 '\-chain=' + /// Chain selection options: + /// + /// -chain= + /// Use the chain (default: main). Allowed values: main, test, signet, regtest + /// ``` + pub fn from_core_arg(core_arg: &str) -> Result { + use Network::*; + + let network = match core_arg { + "main" => Bitcoin, + "test" => Testnet, + "signet" => Signet, + "regtest" => Regtest, + _ => return Err(ParseNetworkError(core_arg.to_owned())), + }; + Ok(network) + } + + /// Return the network's chain hash (genesis block hash). + /// + /// # Examples + /// + /// ```rust + /// use bitcoin::Network; + /// use bitcoin::blockdata::constants::ChainHash; + /// + /// let network = Network::Bitcoin; + /// assert_eq!(network.chain_hash(), ChainHash::BITCOIN); + /// ``` + pub fn chain_hash(self) -> ChainHash { ChainHash::using_genesis_block(self) } + + /// Creates a `Network` from the chain hash (genesis block hash). + /// + /// # Examples + /// + /// ```rust + /// use bitcoin::Network; + /// use bitcoin::blockdata::constants::ChainHash; + /// use std::convert::TryFrom; + /// + /// assert_eq!(Ok(Network::Bitcoin), Network::try_from(ChainHash::BITCOIN)); + /// ``` + pub fn from_chain_hash(chain_hash: ChainHash) -> Option { + Network::try_from(chain_hash).ok() + } +} + +#[cfg(feature = "serde")] +pub mod as_core_arg { + //! Module for serialization/deserialization of network variants into/from Bitcoin Core values + #![allow(missing_docs)] + + use serde; + + use crate::Network; + + pub fn serialize(network: &Network, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(network.to_core_arg()) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct NetworkVisitor; + + impl<'de> serde::de::Visitor<'de> for NetworkVisitor { + type Value = Network; + + fn visit_str(self, s: &str) -> Result { + Network::from_core_arg(s).map_err(|_| { + E::invalid_value( + serde::de::Unexpected::Str(s), + &"bitcoin network encoded as a string (either main, test, signet or regtest)", + ) + }) + } + + fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { + write!( + formatter, + "bitcoin network encoded as a string (either main, test, signet or regtest)" + ) + } + } + + deserializer.deserialize_str(NetworkVisitor) + } +} + +/// An error in parsing network string. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ParseNetworkError(String); + +impl fmt::Display for ParseNetworkError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write_err!(f, "failed to parse {} as network", self.0; self) + } +} +impl_std_error!(ParseNetworkError); + +impl FromStr for Network { + type Err = ParseNetworkError; + + #[inline] + fn from_str(s: &str) -> Result { + use Network::*; + + let network = match s { + "bitcoin" => Bitcoin, + "testnet" => Testnet, + "signet" => Signet, + "regtest" => Regtest, + _ => return Err(ParseNetworkError(s.to_owned())), + }; + Ok(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) + } +} + +/// Error in parsing network from chain hash. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct UnknownChainHash(ChainHash); + +impl Display for UnknownChainHash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "unknown chain hash: {}", self.0) + } +} + +impl_std_error!(UnknownChainHash); + +impl TryFrom for Network { + type Error = UnknownChainHash; + + fn try_from(chain_hash: ChainHash) -> Result { + match chain_hash { + // Note: any new network entries must be matched against here. + ChainHash::BITCOIN => Ok(Network::Bitcoin), + ChainHash::TESTNET => Ok(Network::Testnet), + ChainHash::SIGNET => Ok(Network::Signet), + ChainHash::REGTEST => Ok(Network::Regtest), + _ => Err(UnknownChainHash(chain_hash)), + } + } +} + +#[cfg(test)] +mod tests { + use super::Network; + use crate::consensus::encode::{deserialize, serialize}; + use crate::p2p::ServiceFlags; + + #[test] + fn serialize_test() { + assert_eq!(serialize(&Network::Bitcoin.magic()), &[0xf9, 0xbe, 0xb4, 0xd9]); + assert_eq!(serialize(&Network::Testnet.magic()), &[0x0b, 0x11, 0x09, 0x07]); + assert_eq!(serialize(&Network::Signet.magic()), &[0x0a, 0x03, 0xcf, 0x40]); + assert_eq!(serialize(&Network::Regtest.magic()), &[0xfa, 0xbf, 0xb5, 0xda]); + + assert_eq!(deserialize(&[0xf9, 0xbe, 0xb4, 0xd9]).ok(), Some(Network::Bitcoin.magic())); + assert_eq!(deserialize(&[0x0b, 0x11, 0x09, 0x07]).ok(), Some(Network::Testnet.magic())); + assert_eq!(deserialize(&[0x0a, 0x03, 0xcf, 0x40]).ok(), Some(Network::Signet.magic())); + assert_eq!(deserialize(&[0xfa, 0xbf, 0xb5, 0xda]).ok(), Some(Network::Regtest.magic())); + } + + #[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!(Network::Signet.to_string(), "signet"); + + assert_eq!("bitcoin".parse::().unwrap(), Network::Bitcoin); + assert_eq!("testnet".parse::().unwrap(), Network::Testnet); + assert_eq!("regtest".parse::().unwrap(), Network::Regtest); + assert_eq!("signet".parse::().unwrap(), Network::Signet); + assert!("fakenet".parse::().is_err()); + } + + #[test] + fn service_flags_test() { + let all = [ + ServiceFlags::NETWORK, + ServiceFlags::GETUTXO, + ServiceFlags::BLOOM, + ServiceFlags::WITNESS, + ServiceFlags::COMPACT_FILTERS, + 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); + + flags2 |= ServiceFlags::COMPACT_FILTERS; + flags2 ^= ServiceFlags::GETUTXO; + assert_eq!(flags2, ServiceFlags::COMPACT_FILTERS); + + // 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|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); + } + } + + #[test] + fn from_to_core_arg() { + let expected_pairs = [ + (Network::Bitcoin, "main"), + (Network::Testnet, "test"), + (Network::Regtest, "regtest"), + (Network::Signet, "signet"), + ]; + + for (net, core_arg) in &expected_pairs { + assert_eq!(Network::from_core_arg(core_arg), Ok(*net)); + assert_eq!(net.to_core_arg(), *core_arg); + } + } + + #[cfg(feature = "serde")] + #[test] + fn serde_as_core_arg() { + #[derive(Serialize, Deserialize, PartialEq, Debug)] + #[serde(crate = "actual_serde")] + struct T { + #[serde(with = "crate::network::as_core_arg")] + pub network: Network, + } + + serde_test::assert_tokens( + &T { network: Network::Bitcoin }, + &[ + serde_test::Token::Struct { name: "T", len: 1 }, + serde_test::Token::Str("network"), + serde_test::Token::Str("main"), + serde_test::Token::StructEnd, + ], + ); + } +} diff --git a/bitcoin/src/network/mod.rs b/bitcoin/src/network/mod.rs deleted file mode 100644 index 2e379686..00000000 --- a/bitcoin/src/network/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! Bitcoin network support. -//! -//! This module defines support for (de)serialization and network transport -//! of Bitcoin data and network messages. -//! - -pub mod constants; - -#[cfg(feature = "std")] -pub mod address; -#[cfg(feature = "std")] -pub use self::address::Address; -#[cfg(feature = "std")] -pub mod message; -#[cfg(feature = "std")] -pub mod message_blockdata; -#[cfg(feature = "std")] -pub mod message_bloom; -#[cfg(feature = "std")] -pub mod message_compact_blocks; -#[cfg(feature = "std")] -pub mod message_filter; -#[cfg(feature = "std")] -pub mod message_network; - -pub use self::constants::Magic; diff --git a/bitcoin/src/network/address.rs b/bitcoin/src/p2p/address.rs similarity index 99% rename from bitcoin/src/network/address.rs rename to bitcoin/src/p2p/address.rs index f20907c3..58678bc9 100644 --- a/bitcoin/src/network/address.rs +++ b/bitcoin/src/p2p/address.rs @@ -11,7 +11,7 @@ use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSoc use crate::consensus::encode::{self, Decodable, Encodable, ReadExt, VarInt, WriteExt}; use crate::io; -use crate::network::constants::ServiceFlags; +use crate::p2p::ServiceFlags; use crate::prelude::*; /// A message which can be sent on the Bitcoin network @@ -311,7 +311,7 @@ mod test { use super::{AddrV2, AddrV2Message, Address}; use crate::consensus::encode::{deserialize, serialize}; use crate::internal_macros::hex; - use crate::network::constants::ServiceFlags; + use crate::p2p::ServiceFlags; #[test] fn serialize_address_test() { diff --git a/bitcoin/src/network/message.rs b/bitcoin/src/p2p/message.rs similarity index 98% rename from bitcoin/src/network/message.rs rename to bitcoin/src/p2p/message.rs index 75d22de6..8b048af2 100644 --- a/bitcoin/src/network/message.rs +++ b/bitcoin/src/p2p/message.rs @@ -16,10 +16,10 @@ use crate::blockdata::{block, transaction}; use crate::consensus::encode::{self, CheckedData, Decodable, Encodable, VarInt}; use crate::io; use crate::merkle_tree::MerkleBlock; -use crate::network::address::{AddrV2Message, Address}; -use crate::network::constants::Magic; -use crate::network::{ +use crate::p2p::address::{AddrV2Message, Address}; +use crate::p2p::{ message_blockdata, message_bloom, message_compact_blocks, message_filter, message_network, + Magic, }; use crate::prelude::*; @@ -549,14 +549,15 @@ mod test { use crate::blockdata::transaction::Transaction; use crate::consensus::encode::{deserialize, deserialize_partial, serialize}; use crate::internal_macros::hex; - use crate::network::address::{AddrV2, AddrV2Message, Address}; - use crate::network::constants::{Magic, Network, ServiceFlags}; - use crate::network::message_blockdata::{GetBlocksMessage, GetHeadersMessage, Inventory}; - use crate::network::message_bloom::{BloomFlags, FilterAdd, FilterLoad}; - use crate::network::message_compact_blocks::{GetBlockTxn, SendCmpct}; - use crate::network::message_filter::{ + use crate::network::Network; + use crate::p2p::address::{AddrV2, AddrV2Message, Address}; + use crate::p2p::message_blockdata::{GetBlocksMessage, GetHeadersMessage, Inventory}; + use crate::p2p::message_bloom::{BloomFlags, FilterAdd, FilterLoad}; + use crate::p2p::message_compact_blocks::{GetBlockTxn, SendCmpct}; + use crate::p2p::message_filter::{ CFCheckpt, CFHeaders, CFilter, GetCFCheckpt, GetCFHeaders, GetCFilters, }; + use crate::p2p::{Magic, ServiceFlags}; fn hash(slice: [u8; 32]) -> Hash { Hash::from_slice(&slice).unwrap() } diff --git a/bitcoin/src/network/message_blockdata.rs b/bitcoin/src/p2p/message_blockdata.rs similarity index 96% rename from bitcoin/src/network/message_blockdata.rs rename to bitcoin/src/p2p/message_blockdata.rs index 64273b7b..3a563097 100644 --- a/bitcoin/src/network/message_blockdata.rs +++ b/bitcoin/src/p2p/message_blockdata.rs @@ -11,9 +11,8 @@ use hashes::{sha256d, Hash as _}; use crate::consensus::encode::{self, Decodable, Encodable}; use crate::hash_types::{BlockHash, Txid, Wtxid}; use crate::internal_macros::impl_consensus_encoding; -use crate::io; -use crate::network::constants; use crate::prelude::*; +use crate::{io, p2p}; /// An inventory item. #[derive(PartialEq, Eq, Clone, Debug, Copy, Hash, PartialOrd, Ord)] @@ -128,7 +127,7 @@ pub struct GetHeadersMessage { impl GetBlocksMessage { /// Construct a new `getblocks` message pub fn new(locator_hashes: Vec, stop_hash: BlockHash) -> GetBlocksMessage { - GetBlocksMessage { version: constants::PROTOCOL_VERSION, locator_hashes, stop_hash } + GetBlocksMessage { version: p2p::PROTOCOL_VERSION, locator_hashes, stop_hash } } } @@ -137,7 +136,7 @@ impl_consensus_encoding!(GetBlocksMessage, version, locator_hashes, stop_hash); impl GetHeadersMessage { /// Construct a new `getheaders` message pub fn new(locator_hashes: Vec, stop_hash: BlockHash) -> GetHeadersMessage { - GetHeadersMessage { version: constants::PROTOCOL_VERSION, locator_hashes, stop_hash } + GetHeadersMessage { version: p2p::PROTOCOL_VERSION, locator_hashes, stop_hash } } } diff --git a/bitcoin/src/network/message_bloom.rs b/bitcoin/src/p2p/message_bloom.rs similarity index 100% rename from bitcoin/src/network/message_bloom.rs rename to bitcoin/src/p2p/message_bloom.rs diff --git a/bitcoin/src/network/message_compact_blocks.rs b/bitcoin/src/p2p/message_compact_blocks.rs similarity index 100% rename from bitcoin/src/network/message_compact_blocks.rs rename to bitcoin/src/p2p/message_compact_blocks.rs diff --git a/bitcoin/src/network/message_filter.rs b/bitcoin/src/p2p/message_filter.rs similarity index 100% rename from bitcoin/src/network/message_filter.rs rename to bitcoin/src/p2p/message_filter.rs diff --git a/bitcoin/src/network/message_network.rs b/bitcoin/src/p2p/message_network.rs similarity index 97% rename from bitcoin/src/network/message_network.rs rename to bitcoin/src/p2p/message_network.rs index b49974f5..fad323f6 100644 --- a/bitcoin/src/network/message_network.rs +++ b/bitcoin/src/p2p/message_network.rs @@ -10,10 +10,10 @@ use hashes::sha256d; use crate::consensus::{encode, Decodable, Encodable, ReadExt}; use crate::internal_macros::impl_consensus_encoding; -use crate::io; -use crate::network::address::Address; -use crate::network::constants::{self, ServiceFlags}; +use crate::p2p::address::Address; +use crate::p2p::ServiceFlags; use crate::prelude::*; +use crate::{io, p2p}; /// Some simple messages @@ -54,7 +54,7 @@ impl VersionMessage { start_height: i32, ) -> VersionMessage { VersionMessage { - version: constants::PROTOCOL_VERSION, + version: p2p::PROTOCOL_VERSION, services, timestamp, receiver, @@ -146,7 +146,7 @@ mod tests { use super::{Reject, RejectReason, VersionMessage}; use crate::consensus::encode::{deserialize, serialize}; use crate::internal_macros::hex; - use crate::network::constants::ServiceFlags; + use crate::p2p::ServiceFlags; #[test] fn version_message_test() { diff --git a/bitcoin/src/network/constants.rs b/bitcoin/src/p2p/mod.rs similarity index 52% rename from bitcoin/src/network/constants.rs rename to bitcoin/src/p2p/mod.rs index 1b4d172f..f4b7698c 100644 --- a/bitcoin/src/network/constants.rs +++ b/bitcoin/src/p2p/mod.rs @@ -1,51 +1,45 @@ // SPDX-License-Identifier: CC0-1.0 -//! Bitcoin network constants. +//! Bitcoin p2p network types. //! -//! This module provides various constants relating to the Bitcoin network -//! protocol, such as protocol versioning and magic header bytes. -//! -//! The [`Network`][1] type implements the [`Decodable`][2] and -//! [`Encodable`][3] traits and encodes the magic bytes of the given -//! network. -//! -//! [1]: enum.Network.html -//! [2]: ../../consensus/encode/trait.Decodable.html -//! [3]: ../../consensus/encode/trait.Encodable.html -//! -//! # Example: encoding a network's magic bytes -//! -//! ```rust -//! use bitcoin::network::constants::Network; -//! use bitcoin::consensus::encode::serialize; -//! -//! let network = Network::Bitcoin; -//! let bytes = serialize(&network.magic()); -//! -//! assert_eq!(&bytes[..], &[0xF9, 0xBE, 0xB4, 0xD9]); -//! ``` +//! This module defines support for (de)serialization and network transport +//! of Bitcoin data and Bitcoin p2p network messages. + +#[cfg(feature = "std")] +pub mod address; +#[cfg(feature = "std")] +pub use self::address::Address; +#[cfg(feature = "std")] +pub mod message; +#[cfg(feature = "std")] +pub mod message_blockdata; +#[cfg(feature = "std")] +pub mod message_bloom; +#[cfg(feature = "std")] +pub mod message_compact_blocks; +#[cfg(feature = "std")] +pub mod message_filter; +#[cfg(feature = "std")] +pub mod message_network; -use core::borrow::{Borrow, BorrowMut}; use core::convert::TryFrom; -use core::fmt::Display; use core::str::FromStr; use core::{fmt, ops}; use hex::FromHex; use internals::{debug_from_display, write_err}; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; use crate::consensus::encode::{self, Decodable, Encodable}; -use crate::constants::ChainHash; use crate::error::impl_std_error; -use crate::io; -use crate::prelude::{String, ToOwned}; +use crate::prelude::{Borrow, BorrowMut, String, ToOwned}; +use crate::{io, Network}; -/// Version of the protocol as appearing in network message headers -/// This constant is used to signal to other peers which features you support. -/// Increasing it implies that your software also supports every feature prior to this version. -/// Doing so without support may lead to you incorrectly banning other peers or other peers banning you. +/// Version of the protocol as appearing in network message headers. +/// +/// This constant is used to signal to other peers which features you support. Increasing it implies +/// that your software also supports every feature prior to this version. Doing so without support +/// may lead to you incorrectly banning other peers or other peers banning you. +/// /// These are the features required for each version: /// 70016 - Support receiving `wtxidrelay` message between `version` and `verack` message /// 70015 - Support receiving invalid compact blocks from a peer without banning them @@ -59,387 +53,6 @@ use crate::prelude::{String, ToOwned}; /// 60001 - Support `pong` message and nonce in `ping` message pub const PROTOCOL_VERSION: u32 = 70001; -/// 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 { - /// Creates a `Network` from the magic bytes. - /// - /// # Examples - /// - /// ```rust - /// use bitcoin::network::constants::{Network, Magic}; - /// use std::convert::TryFrom; - /// - /// 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: Magic) -> Option { Network::try_from(magic).ok() } - - /// Return the network magic bytes, which should be encoded little-endian - /// at the start of every message - /// - /// # Examples - /// - /// ```rust - /// use bitcoin::network::constants::{Network, Magic}; - /// - /// let network = Network::Bitcoin; - /// assert_eq!(network.magic(), Magic::from_bytes([0xF9, 0xBE, 0xB4, 0xD9])); - /// ``` - pub fn magic(self) -> Magic { Magic::from(self) } - - /// Converts a `Network` to its equivalent `bitcoind -chain` argument name. - /// - /// ```bash - /// $ bitcoin-23.0/bin/bitcoind --help | grep -C 3 '\-chain=' - /// Chain selection options: - /// - /// -chain= - /// Use the chain (default: main). Allowed values: main, test, signet, regtest - /// ``` - pub fn to_core_arg(self) -> &'static str { - match self { - Network::Bitcoin => "main", - Network::Testnet => "test", - Network::Signet => "signet", - Network::Regtest => "regtest", - } - } - - /// Converts a `bitcoind -chain` argument name to its equivalent `Network`. - /// - /// ```bash - /// $ bitcoin-23.0/bin/bitcoind --help | grep -C 3 '\-chain=' - /// Chain selection options: - /// - /// -chain= - /// Use the chain (default: main). Allowed values: main, test, signet, regtest - /// ``` - pub fn from_core_arg(core_arg: &str) -> Result { - use Network::*; - - let network = match core_arg { - "main" => Bitcoin, - "test" => Testnet, - "signet" => Signet, - "regtest" => Regtest, - _ => return Err(ParseNetworkError(core_arg.to_owned())), - }; - Ok(network) - } - - /// Return the network's chain hash (genesis block hash). - /// - /// # Examples - /// - /// ```rust - /// use bitcoin::network::constants::Network; - /// use bitcoin::blockdata::constants::ChainHash; - /// - /// let network = Network::Bitcoin; - /// assert_eq!(network.chain_hash(), ChainHash::BITCOIN); - /// ``` - pub fn chain_hash(self) -> ChainHash { ChainHash::using_genesis_block(self) } - - /// Creates a `Network` from the chain hash (genesis block hash). - /// - /// # Examples - /// - /// ```rust - /// use bitcoin::network::constants::Network; - /// use bitcoin::blockdata::constants::ChainHash; - /// use std::convert::TryFrom; - /// - /// assert_eq!(Ok(Network::Bitcoin), Network::try_from(ChainHash::BITCOIN)); - /// ``` - pub fn from_chain_hash(chain_hash: ChainHash) -> Option { - Network::try_from(chain_hash).ok() - } -} - -#[cfg(feature = "serde")] -pub mod as_core_arg { - //! Module for serialization/deserialization of network variants into/from Bitcoin Core values - #![allow(missing_docs)] - - use serde; - - use crate::Network; - - pub fn serialize(network: &Network, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(network.to_core_arg()) - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct NetworkVisitor; - - impl<'de> serde::de::Visitor<'de> for NetworkVisitor { - type Value = Network; - - fn visit_str(self, s: &str) -> Result { - Network::from_core_arg(s).map_err(|_| { - E::invalid_value( - serde::de::Unexpected::Str(s), - &"bitcoin network encoded as a string (either main, test, signet or regtest)", - ) - }) - } - - fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { - write!( - formatter, - "bitcoin network encoded as a string (either main, test, signet or regtest)" - ) - } - } - - deserializer.deserialize_str(NetworkVisitor) - } -} - -/// An error in parsing network string. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ParseNetworkError(String); - -impl fmt::Display for ParseNetworkError { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write_err!(f, "failed to parse {} as network", self.0; self) - } -} -impl_std_error!(ParseNetworkError); - -impl FromStr for Network { - type Err = ParseNetworkError; - - #[inline] - fn from_str(s: &str) -> Result { - use Network::*; - - let network = match s { - "bitcoin" => Bitcoin, - "testnet" => Testnet, - "signet" => Signet, - "regtest" => Regtest, - _ => return Err(ParseNetworkError(s.to_owned())), - }; - Ok(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) - } -} - -/// Error in parsing network from chain hash. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct UnknownChainHash(ChainHash); - -impl Display for UnknownChainHash { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "unknown chain hash: {}", self.0) - } -} - -impl_std_error!(UnknownChainHash); - -impl TryFrom for Network { - type Error = UnknownChainHash; - - fn try_from(chain_hash: ChainHash) -> Result { - match chain_hash { - // Note: any new network entries must be matched against here. - ChainHash::BITCOIN => Ok(Network::Bitcoin), - ChainHash::TESTNET => Ok(Network::Testnet), - ChainHash::SIGNET => Ok(Network::Signet), - ChainHash::REGTEST => Ok(Network::Regtest), - _ => Err(UnknownChainHash(chain_hash)), - } - } -} - -/// 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, Clone, PartialEq, Eq)] -pub struct ParseMagicError { - /// The error that occurred when parsing the string. - error: hex::HexToArrayError, - /// 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 creating a Network from Magic bytes. -#[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> { - hex::fmt_hex_exact!(f, 4, &self.0, hex::Case::Lower)?; - Ok(()) - } -} -debug_from_display!(Magic); - -impl fmt::LowerHex for Magic { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - hex::fmt_hex_exact!(f, 4, &self.0, hex::Case::Lower)?; - Ok(()) - } -} - -impl fmt::UpperHex for Magic { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - hex::fmt_hex_exact!(f, 4, &self.0, 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 AsMut<[u8]> for Magic { - fn as_mut(&mut self) -> &mut [u8] { &mut self.0 } -} - -impl AsMut<[u8; 4]> for Magic { - fn as_mut(&mut self) -> &mut [u8; 4] { &mut 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, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ServiceFlags(u64); @@ -587,41 +200,161 @@ impl Decodable for ServiceFlags { Ok(ServiceFlags(Decodable::consensus_decode(r)?)) } } +/// 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: hex::HexToArrayError, + /// 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 creating a Network from Magic bytes. +#[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> { + hex::fmt_hex_exact!(f, 4, &self.0, hex::Case::Lower)?; + Ok(()) + } +} +debug_from_display!(Magic); + +impl fmt::LowerHex for Magic { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + hex::fmt_hex_exact!(f, 4, &self.0, hex::Case::Lower)?; + Ok(()) + } +} + +impl fmt::UpperHex for Magic { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + hex::fmt_hex_exact!(f, 4, &self.0, 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 AsMut<[u8]> for Magic { + fn as_mut(&mut self) -> &mut [u8] { &mut self.0 } +} + +impl AsMut<[u8; 4]> for Magic { + fn as_mut(&mut self) -> &mut [u8; 4] { &mut 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); #[cfg(test)] mod tests { - use std::convert::TryFrom; - use std::str::FromStr; - - use super::{Magic, Network, ServiceFlags}; - use crate::consensus::encode::{deserialize, serialize}; - - #[test] - fn serialize_test() { - assert_eq!(serialize(&Network::Bitcoin.magic()), &[0xf9, 0xbe, 0xb4, 0xd9]); - assert_eq!(serialize(&Network::Testnet.magic()), &[0x0b, 0x11, 0x09, 0x07]); - assert_eq!(serialize(&Network::Signet.magic()), &[0x0a, 0x03, 0xcf, 0x40]); - assert_eq!(serialize(&Network::Regtest.magic()), &[0xfa, 0xbf, 0xb5, 0xda]); - - assert_eq!(deserialize(&[0xf9, 0xbe, 0xb4, 0xd9]).ok(), Some(Network::Bitcoin.magic())); - assert_eq!(deserialize(&[0x0b, 0x11, 0x09, 0x07]).ok(), Some(Network::Testnet.magic())); - assert_eq!(deserialize(&[0x0a, 0x03, 0xcf, 0x40]).ok(), Some(Network::Signet.magic())); - assert_eq!(deserialize(&[0xfa, 0xbf, 0xb5, 0xda]).ok(), Some(Network::Regtest.magic())); - } - - #[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!(Network::Signet.to_string(), "signet"); - - assert_eq!("bitcoin".parse::().unwrap(), Network::Bitcoin); - assert_eq!("testnet".parse::().unwrap(), Network::Testnet); - assert_eq!("regtest".parse::().unwrap(), Network::Regtest); - assert_eq!("signet".parse::().unwrap(), Network::Signet); - assert!("fakenet".parse::().is_err()); - } + use super::*; #[test] fn service_flags_test() { @@ -663,29 +396,6 @@ mod tests { 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); - } - } - #[test] fn magic_from_str() { let known_network_magic_strs = [ @@ -701,40 +411,4 @@ mod tests { assert_eq!(&magic.to_string(), magic_str); } } - - #[test] - fn from_to_core_arg() { - let expected_pairs = [ - (Network::Bitcoin, "main"), - (Network::Testnet, "test"), - (Network::Regtest, "regtest"), - (Network::Signet, "signet"), - ]; - - for (net, core_arg) in &expected_pairs { - assert_eq!(Network::from_core_arg(core_arg), Ok(*net)); - assert_eq!(net.to_core_arg(), *core_arg); - } - } - - #[cfg(feature = "serde")] - #[test] - fn serde_as_core_arg() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - #[serde(crate = "actual_serde")] - struct T { - #[serde(with = "crate::network::constants::as_core_arg")] - pub network: Network, - } - - serde_test::assert_tokens( - &T { network: Network::Bitcoin }, - &[ - serde_test::Token::Struct { name: "T", len: 1 }, - serde_test::Token::Str("network"), - serde_test::Token::Str("main"), - serde_test::Token::StructEnd, - ], - ); - } } diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs index ee378360..df9f6c90 100644 --- a/bitcoin/src/psbt/mod.rs +++ b/bitcoin/src/psbt/mod.rs @@ -823,7 +823,7 @@ mod tests { use crate::blockdata::transaction::{OutPoint, Sequence, Transaction, TxIn, TxOut}; use crate::blockdata::witness::Witness; use crate::internal_macros::hex; - use crate::network::constants::Network::Bitcoin; + use crate::network::Network::Bitcoin; use crate::psbt::map::{Input, Output}; use crate::psbt::raw; use crate::psbt::serialize::{Deserialize, Serialize}; diff --git a/fuzz/fuzz_targets/bitcoin/deser_net_msg.rs b/fuzz/fuzz_targets/bitcoin/deser_net_msg.rs index 883431c1..906bcee9 100644 --- a/fuzz/fuzz_targets/bitcoin/deser_net_msg.rs +++ b/fuzz/fuzz_targets/bitcoin/deser_net_msg.rs @@ -1,7 +1,7 @@ use honggfuzz::fuzz; fn do_test(data: &[u8]) { - let _: Result = + let _: Result = bitcoin::consensus::encode::deserialize(data); } diff --git a/fuzz/fuzz_targets/bitcoin/deserialize_script.rs b/fuzz/fuzz_targets/bitcoin/deserialize_script.rs index 1020e0b0..a32f01f0 100644 --- a/fuzz/fuzz_targets/bitcoin/deserialize_script.rs +++ b/fuzz/fuzz_targets/bitcoin/deserialize_script.rs @@ -1,7 +1,7 @@ use bitcoin::address::Address; use bitcoin::blockdata::script; use bitcoin::consensus::encode; -use bitcoin::network::constants::Network; +use bitcoin::Network; use honggfuzz::fuzz; fn do_test(data: &[u8]) {