diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index 1ff3ceaa3..9667527dd 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -77,6 +77,7 @@ name = "bitcoin-fuzz" version = "0.0.1" dependencies = [ "bitcoin", + "bitcoin-p2p-messages", "honggfuzz", "serde", "serde_json", @@ -109,6 +110,15 @@ dependencies = [ [[package]] name = "bitcoin-p2p-messages" version = "0.1.0" +dependencies = [ + "bitcoin", + "bitcoin-internals", + "bitcoin-io 0.2.0", + "bitcoin-units", + "bitcoin_hashes 0.16.0", + "hex-conservative 0.3.0", + "hex_lit", +] [[package]] name = "bitcoin-primitives" diff --git a/Cargo-recent.lock b/Cargo-recent.lock index edfae1793..ded29f2b8 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -76,6 +76,7 @@ name = "bitcoin-fuzz" version = "0.0.1" dependencies = [ "bitcoin", + "bitcoin-p2p-messages", "honggfuzz", "serde", "serde_json", @@ -108,6 +109,15 @@ dependencies = [ [[package]] name = "bitcoin-p2p-messages" version = "0.1.0" +dependencies = [ + "bitcoin", + "bitcoin-internals", + "bitcoin-io 0.2.0", + "bitcoin-units", + "bitcoin_hashes 0.16.0", + "hex-conservative 0.3.0", + "hex_lit", +] [[package]] name = "bitcoin-primitives" diff --git a/bitcoin/Cargo.toml b/bitcoin/Cargo.toml index f6bff55c9..2e3ff37a6 100644 --- a/bitcoin/Cargo.toml +++ b/bitcoin/Cargo.toml @@ -56,10 +56,6 @@ rustdoc-args = ["--cfg", "docsrs"] [[example]] name = "bip32" -[[example]] -name = "handshake" -required-features = ["rand-std"] - [[example]] name = "ecdsa-psbt" required-features = ["std", "bitcoinconsensus"] diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs index 60e151355..d30521926 100644 --- a/bitcoin/src/lib.rs +++ b/bitcoin/src/lib.rs @@ -94,7 +94,6 @@ extern crate serde; mod internal_macros; #[macro_use] -pub mod p2p; pub mod address; pub mod bip152; pub mod bip158; diff --git a/bitcoin/src/p2p/deser.rs b/bitcoin/src/p2p/deser.rs deleted file mode 100644 index cae2906c3..000000000 --- a/bitcoin/src/p2p/deser.rs +++ /dev/null @@ -1,42 +0,0 @@ -macro_rules! impl_vec_wrapper { - ($wrapper: ident, $type: ty) => { - impl crate::consensus::encode::Encodable for $wrapper { - #[inline] - fn consensus_encode( - &self, - w: &mut W, - ) -> core::result::Result { - let mut len = 0; - len += w.emit_compact_size(self.0.len())?; - for c in self.0.iter() { - len += c.consensus_encode(w)?; - } - Ok(len) - } - } - - impl crate::consensus::encode::Decodable for $wrapper { - #[inline] - fn consensus_decode_from_finite_reader( - r: &mut R, - ) -> core::result::Result<$wrapper, crate::consensus::encode::Error> { - let len = r.read_compact_size()?; - // Do not allocate upfront more items than if the sequence of type - // occupied roughly quarter a block. This should never be the case - // for normal data, but even if that's not true - `push` will just - // reallocate. - // Note: OOM protection relies on reader eventually running out of - // data to feed us. - let max_capacity = - crate::consensus::encode::MAX_VEC_SIZE / 4 / core::mem::size_of::<$type>(); - let mut ret = Vec::with_capacity(core::cmp::min(len as usize, max_capacity)); - for _ in 0..len { - ret.push(Decodable::consensus_decode_from_finite_reader(r)?); - } - Ok($wrapper(ret)) - } - } - }; -} - -pub(crate) use impl_vec_wrapper; diff --git a/bitcoin/src/p2p/mod.rs b/bitcoin/src/p2p/mod.rs deleted file mode 100644 index 0f6b2ccae..000000000 --- a/bitcoin/src/p2p/mod.rs +++ /dev/null @@ -1,495 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! Bitcoin p2p network types. -//! -//! 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")] -mod deser; -#[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::str::FromStr; -use core::{fmt, ops}; - -use hex::FromHex; -use internals::{impl_to_hex_from_lower_hex, write_err}; -use io::{BufRead, Write}; - -use crate::consensus::encode::{self, Decodable, Encodable}; -use crate::network::{Network, Params, TestnetVersion}; -use crate::prelude::{Borrow, BorrowMut, String, ToOwned}; - -#[rustfmt::skip] -#[doc(inline)] -#[cfg(feature = "std")] -pub use self::address::Address; - -/// 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 -/// 70014 - Support compact block messages `sendcmpct`, `cmpctblock`, `getblocktxn` and `blocktxn` -/// 70013 - Support `feefilter` message -/// 70012 - Support `sendheaders` message and announce new blocks via headers rather than inv -/// 70011 - Support NODE_BLOOM service flag and don't support bloom filter messages if it is not set -/// 70002 - Support `reject` message -/// 70001 - Support bloom filter messages `filterload`, `filterclear` `filteradd`, `merkleblock` and FILTERED_BLOCK inventory type -/// 60002 - Support `mempool` message -/// 60001 - Support `pong` message and nonce in `ping` message -pub const PROTOCOL_VERSION: u32 = 70001; - -/// Flags to indicate which network services a node supports. -#[derive(Debug, Clone, Copy, Default, 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); - - /// COMPACT_FILTERS means the node will service basic block filter requests. - /// See BIP157 and BIP158 for details on how this is implemented. - pub const COMPACT_FILTERS: ServiceFlags = ServiceFlags(1 << 6); - - /// 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); - - /// P2P_V2 indicates that the node supports the P2P v2 encrypted transport protocol. - /// See BIP324 for details on how this is implemented. - pub const P2P_V2: ServiceFlags = ServiceFlags(1 << 11); - - // 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 } - - /// Gets the integer representation of this [`ServiceFlags`]. - pub fn to_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_to_hex_from_lower_hex!(ServiceFlags, |service_flags: &ServiceFlags| 16 - - service_flags.0.leading_zeros() as usize / 4); - -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 { - let mut flags = *self; - if flags == ServiceFlags::NONE { - return write!(f, "ServiceFlags(NONE)"); - } - 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!(COMPACT_FILTERS); - write_flag!(NETWORK_LIMITED); - write_flag!(P2P_V2); - // 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 From for u64 { - fn from(flags: ServiceFlags) -> Self { flags.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, w: &mut W) -> Result { - self.0.consensus_encode(w) - } -} - -impl Decodable for ServiceFlags { - #[inline] - fn consensus_decode(r: &mut R) -> Result { - 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 testnet3 network magic bytes. - #[deprecated(since = "0.33.0", note = "use `TESTNET3` instead")] - pub const TESTNET: Self = Self([0x0B, 0x11, 0x09, 0x07]); - /// Bitcoin testnet3 network magic bytes. - pub const TESTNET3: Self = Self([0x0B, 0x11, 0x09, 0x07]); - /// Bitcoin testnet4 network magic bytes. - pub const TESTNET4: Self = Self([0x1c, 0x16, 0x3f, 0x28]); - /// 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]); - - /// Construct a new network magic from bytes. - pub const fn from_bytes(bytes: [u8; 4]) -> Magic { Magic(bytes) } - - /// Get network magic bytes. - pub fn to_bytes(self) -> [u8; 4] { self.0 } - - /// Returns the magic bytes for the network defined by `params`. - pub fn from_params(params: impl AsRef) -> Self { params.as_ref().network.into() } -} - -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) -> Self { - match network { - Network::Bitcoin => Magic::BITCOIN, - Network::Testnet(TestnetVersion::V3) => Magic::TESTNET3, - Network::Testnet(TestnetVersion::V4) => Magic::TESTNET4, - Network::Signet => Magic::SIGNET, - Network::Regtest => Magic::REGTEST, - // Remember to add the `TryFrom` for new networks - } - } -} - -impl TryFrom for Network { - type Error = UnknownMagicError; - - fn try_from(magic: Magic) -> Result { - match magic { - Magic::BITCOIN => Ok(Network::Bitcoin), - Magic::TESTNET3 => Ok(Network::Testnet(TestnetVersion::V3)), - Magic::TESTNET4 => Ok(Network::Testnet(TestnetVersion::V4)), - Magic::SIGNET => Ok(Network::Signet), - Magic::REGTEST => Ok(Network::Regtest), - _ => Err(UnknownMagicError(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(()) - } -} - -impl fmt::Debug for Magic { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { fmt::Display::fmt(self, f) } -} - -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_to_hex_from_lower_hex!(Magic, |_| 8); - -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 } -} - -/// An error in parsing magic bytes. -#[derive(Debug, Clone, PartialEq, Eq)] -#[non_exhaustive] -pub struct ParseMagicError { - /// The error that occurred when parsing the string. - error: hex::HexToArrayError, - /// The byte string that failed to parse. - magic: String, -} - -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) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for ParseMagicError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.error) } -} - -/// Error in creating a Network from Magic bytes. -#[derive(Debug, Clone, PartialEq, Eq)] -#[non_exhaustive] -pub struct UnknownMagicError(Magic); - -impl fmt::Display for UnknownMagicError { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write!(f, "unknown network magic {}", self.0) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for UnknownMagicError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::consensus::encode::{deserialize, serialize}; - - #[test] - fn serialize_deserialize() { - assert_eq!(serialize(&Magic::BITCOIN), &[0xf9, 0xbe, 0xb4, 0xd9]); - let magic: Magic = Network::Bitcoin.into(); - assert_eq!(serialize(&magic), &[0xf9, 0xbe, 0xb4, 0xd9]); - assert_eq!(serialize(&Magic::TESTNET3), &[0x0b, 0x11, 0x09, 0x07]); - let magic: Magic = Network::Testnet(TestnetVersion::V3).into(); - assert_eq!(serialize(&magic), &[0x0b, 0x11, 0x09, 0x07]); - assert_eq!(serialize(&Magic::TESTNET4), &[0x1c, 0x16, 0x3f, 0x28]); - let magic: Magic = Network::Testnet(TestnetVersion::V4).into(); - assert_eq!(serialize(&magic), &[0x1c, 0x16, 0x3f, 0x28]); - assert_eq!(serialize(&Magic::SIGNET), &[0x0a, 0x03, 0xcf, 0x40]); - let magic: Magic = Network::Signet.into(); - assert_eq!(serialize(&magic), &[0x0a, 0x03, 0xcf, 0x40]); - assert_eq!(serialize(&Magic::REGTEST), &[0xfa, 0xbf, 0xb5, 0xda]); - let magic: Magic = Network::Regtest.into(); - assert_eq!(serialize(&magic), &[0xfa, 0xbf, 0xb5, 0xda]); - - assert_eq!( - deserialize::(&[0xf9, 0xbe, 0xb4, 0xd9]).ok(), - Some(Network::Bitcoin.into()) - ); - assert_eq!( - deserialize::(&[0x0b, 0x11, 0x09, 0x07]).ok(), - Some(Network::Testnet(TestnetVersion::V3).into()) - ); - assert_eq!( - deserialize::(&[0x1c, 0x16, 0x3f, 0x28]).ok(), - Some(Network::Testnet(TestnetVersion::V4).into()) - ); - assert_eq!( - deserialize::(&[0x0a, 0x03, 0xcf, 0x40]).ok(), - Some(Network::Signet.into()) - ); - assert_eq!( - deserialize::(&[0xfa, 0xbf, 0xb5, 0xda]).ok(), - Some(Network::Regtest.into()) - ); - } - - #[test] - fn service_flags_test() { - let all = [ - ServiceFlags::NETWORK, - ServiceFlags::GETUTXO, - ServiceFlags::BLOOM, - ServiceFlags::WITNESS, - ServiceFlags::COMPACT_FILTERS, - ServiceFlags::NETWORK_LIMITED, - ServiceFlags::P2P_V2, - ]; - - 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()); - assert_eq!("ServiceFlags(P2P_V2)", ServiceFlags::P2P_V2.to_string()); - let flag = ServiceFlags::WITNESS - | ServiceFlags::BLOOM - | ServiceFlags::NETWORK - | ServiceFlags::P2P_V2; - assert_eq!("ServiceFlags(NETWORK|BLOOM|WITNESS|P2P_V2)", flag.to_string()); - let flag = ServiceFlags::WITNESS | 0xf0.into(); - assert_eq!("ServiceFlags(WITNESS|COMPACT_FILTERS|0xb0)", flag.to_string()); - } - - #[test] - fn magic_from_str() { - let known_network_magic_strs = [ - ("f9beb4d9", Network::Bitcoin), - ("0b110907", Network::Testnet(TestnetVersion::V3)), - ("1c163f28", Network::Testnet(TestnetVersion::V4)), - ("fabfb5da", Network::Regtest), - ("0a03cf40", Network::Signet), - ]; - - for (magic_str, network) in &known_network_magic_strs { - let magic: Magic = magic_str.parse::().unwrap(); - assert_eq!(Network::try_from(magic).unwrap(), *network); - assert_eq!(&magic.to_string(), magic_str); - } - } -} diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 38adb8b8c..5cf789841 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -11,6 +11,7 @@ cargo-fuzz = true [dependencies] honggfuzz = { version = "0.5.56", default-features = false } bitcoin = { path = "../bitcoin", features = [ "serde" ] } +p2p = { path = "../p2p", package = "bitcoin-p2p-messages" } serde = { version = "1.0.103", features = [ "derive" ] } serde_json = "1.0" diff --git a/fuzz/fuzz_targets/bitcoin/deser_net_msg.rs b/fuzz/fuzz_targets/bitcoin/deser_net_msg.rs index 906bcee9f..635a4a7a0 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/p2p_address_roundtrip.rs b/fuzz/fuzz_targets/bitcoin/p2p_address_roundtrip.rs index 4fa4ffb27..5fba06632 100644 --- a/fuzz/fuzz_targets/bitcoin/p2p_address_roundtrip.rs +++ b/fuzz/fuzz_targets/bitcoin/p2p_address_roundtrip.rs @@ -2,7 +2,7 @@ use std::convert::TryFrom; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use bitcoin::consensus::Decodable; -use bitcoin::p2p::address::AddrV2; +use p2p::address::AddrV2; use honggfuzz::fuzz; fn do_test(data: &[u8]) { diff --git a/p2p/Cargo.toml b/p2p/Cargo.toml index 37166211c..892eee902 100644 --- a/p2p/Cargo.toml +++ b/p2p/Cargo.toml @@ -13,8 +13,18 @@ rust-version = "1.63.0" exclude = ["tests", "contrib"] [dependencies] +bitcoin = { path = "../bitcoin/" } +hashes = { package = "bitcoin_hashes", path = "../hashes", default-features = false, features = ["std"] } +hex = { package = "hex-conservative", version = "0.3.0", default-features = false, features = ["std"] } +internals = { package = "bitcoin-internals", path = "../internals", features = ["std"] } +io = { package = "bitcoin-io", path = "../io", default-features = false, features = ["std"] } +units = { package = "bitcoin-units", path = "../units", default-features = false, features = ["std"] } [dev-dependencies] +hex_lit = "0.1.1" + +[[example]] +name = "handshake" [package.metadata.docs.rs] all-features = true diff --git a/bitcoin/examples/handshake.rs b/p2p/examples/handshake.rs similarity index 90% rename from bitcoin/examples/handshake.rs rename to p2p/examples/handshake.rs index dbc383dd1..ad9194597 100644 --- a/bitcoin/examples/handshake.rs +++ b/p2p/examples/handshake.rs @@ -4,8 +4,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use std::{env, process}; use bitcoin::consensus::{encode, Decodable}; -use bitcoin::p2p::{self, address, message, message_network, Magic}; -use bitcoin::secp256k1::rand::Rng; +use bitcoin_p2p_messages::{self, address, message, message_network, Magic, ServiceFlags}; fn main() { // This example establishes a connection to a Bitcoin node, sends the initial @@ -71,19 +70,20 @@ 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 = p2p::ServiceFlags::NONE; + let services = 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, p2p::ServiceFlags::NONE); + let addr_recv = address::Address::new(&address, ServiceFlags::NONE); // "The network address of the node emitting this message" - let addr_from = address::Address::new(&my_address, p2p::ServiceFlags::NONE); + let addr_from = address::Address::new(&my_address, 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(); + // Because this crate does not include the `rand` dependency, this is a fixed value. + let nonce: u64 = 42; // "User Agent (0x00 if string is 0 bytes long)" let user_agent = String::from("rust-example"); diff --git a/bitcoin/src/p2p/address.rs b/p2p/src/address.rs similarity index 96% rename from bitcoin/src/p2p/address.rs rename to p2p/src/address.rs index 7a4af27e4..f83c70563 100644 --- a/bitcoin/src/p2p/address.rs +++ b/p2p/src/address.rs @@ -10,9 +10,8 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV use io::{BufRead, Read, Write}; -use crate::consensus; -use crate::consensus::encode::{self, Decodable, Encodable, ReadExt, WriteExt}; -use crate::p2p::ServiceFlags; +use bitcoin::consensus::encode::{self, Decodable, Encodable, ReadExt, WriteExt}; +use crate::ServiceFlags; /// A message which can be sent on the Bitcoin network #[derive(Clone, PartialEq, Eq, Hash)] @@ -207,7 +206,7 @@ impl Encodable for AddrV2 { network: u8, bytes: &[u8], ) -> Result { - Ok(network.consensus_encode(w)? + encode::consensus_encode_with_size(bytes, w)?) + Ok(network.consensus_encode(w)? + crate::consensus::consensus_encode_with_size(bytes, w)?) } Ok(match *self { AddrV2::Ipv4(ref addr) => encode_addr(w, 1, &addr.octets())?, @@ -225,28 +224,28 @@ impl Decodable for AddrV2 { let network_id = u8::consensus_decode(r)?; let len = r.read_compact_size()?; if len > 512 { - return Err(consensus::parse_failed_error("IP must be <= 512 bytes")); + return Err(crate::consensus::parse_failed_error("IP must be <= 512 bytes")); } Ok(match network_id { 1 => { if len != 4 { - return Err(consensus::parse_failed_error("invalid IPv4 address")); + return Err(crate::consensus::parse_failed_error("invalid IPv4 address")); } let addr: [u8; 4] = Decodable::consensus_decode(r)?; AddrV2::Ipv4(Ipv4Addr::new(addr[0], addr[1], addr[2], addr[3])) } 2 => { if len != 16 { - return Err(consensus::parse_failed_error("invalid IPv6 address")); + return Err(crate::consensus::parse_failed_error("invalid IPv6 address")); } let addr: [u16; 8] = read_be_address(r)?; if addr[0..3] == ONION { - return Err(consensus::parse_failed_error( + return Err(crate::consensus::parse_failed_error( "OnionCat address sent with IPv6 network id", )); } if addr[0..6] == [0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF] { - return Err(consensus::parse_failed_error( + return Err(crate::consensus::parse_failed_error( "IPV4 wrapped address sent with IPv6 network id", )); } @@ -257,26 +256,26 @@ impl Decodable for AddrV2 { 4 => { if len != 32 { - return Err(consensus::parse_failed_error("invalid TorV3 address")); + return Err(crate::consensus::parse_failed_error("invalid TorV3 address")); } let pubkey = Decodable::consensus_decode(r)?; AddrV2::TorV3(pubkey) } 5 => { if len != 32 { - return Err(consensus::parse_failed_error("invalid I2P address")); + return Err(crate::consensus::parse_failed_error("invalid I2P address")); } let hash = Decodable::consensus_decode(r)?; AddrV2::I2p(hash) } 6 => { if len != 16 { - return Err(consensus::parse_failed_error("invalid CJDNS address")); + return Err(crate::consensus::parse_failed_error("invalid CJDNS address")); } let addr: [u16; 8] = read_be_address(r)?; // check the first byte for the CJDNS marker if addr[0] >> 8 != 0xFC { - return Err(consensus::parse_failed_error("invalid CJDNS address")); + return Err(crate::consensus::parse_failed_error("invalid CJDNS address")); } AddrV2::Cjdns(Ipv6Addr::new( addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7], @@ -438,12 +437,12 @@ impl std::error::Error for AddrV2ToIpv6AddrError {} mod test { use std::net::IpAddr; + use bitcoin::consensus::encode::{deserialize, serialize}; use hex::FromHex; use hex_lit::hex; use super::*; - use crate::consensus::encode::{deserialize, serialize}; - use crate::p2p::message::AddrV2Payload; + use crate::message::AddrV2Payload; #[test] fn serialize_address() { diff --git a/p2p/src/consensus.rs b/p2p/src/consensus.rs new file mode 100644 index 000000000..68ea527d4 --- /dev/null +++ b/p2p/src/consensus.rs @@ -0,0 +1,95 @@ +use bitcoin::consensus::encode::WriteExt; +use io::Write; + +pub(crate) fn consensus_encode_with_size( + data: &[u8], + w: &mut W, +) -> Result { + Ok(w.emit_compact_size(data.len())? + w.emit_slice(data)?) +} + +pub(crate) fn parse_failed_error(msg: &'static str) -> bitcoin::consensus::encode::Error { + bitcoin::consensus::encode::Error::Parse(bitcoin::consensus::encode::ParseError::ParseFailed(msg)) +} + +macro_rules! impl_consensus_encoding { + ($thing:ident, $($field:ident),+) => ( + impl bitcoin::consensus::Encodable for $thing { + #[inline] + fn consensus_encode( + &self, + w: &mut W, + ) -> core::result::Result { + let mut len = 0; + $(len += self.$field.consensus_encode(w)?;)+ + Ok(len) + } + } + + impl bitcoin::consensus::Decodable for $thing { + + #[inline] + fn consensus_decode_from_finite_reader( + r: &mut R, + ) -> core::result::Result<$thing, bitcoin::consensus::encode::Error> { + Ok($thing { + $($field: bitcoin::consensus::Decodable::consensus_decode_from_finite_reader(r)?),+ + }) + } + + #[inline] + fn consensus_decode( + r: &mut R, + ) -> core::result::Result<$thing, bitcoin::consensus::encode::Error> { + let mut r = r.take(internals::ToU64::to_u64(bitcoin::consensus::encode::MAX_VEC_SIZE)); + Ok($thing { + $($field: bitcoin::consensus::Decodable::consensus_decode(&mut r)?),+ + }) + } + } + ); +} +pub(crate) use impl_consensus_encoding; + +macro_rules! impl_vec_wrapper { + ($wrapper: ident, $type: ty) => { + impl bitcoin::consensus::encode::Encodable for $wrapper { + #[inline] + fn consensus_encode( + &self, + w: &mut W, + ) -> core::result::Result { + let mut len = 0; + len += w.emit_compact_size(self.0.len())?; + for c in self.0.iter() { + len += c.consensus_encode(w)?; + } + Ok(len) + } + } + + impl bitcoin::consensus::encode::Decodable for $wrapper { + #[inline] + fn consensus_decode_from_finite_reader( + r: &mut R, + ) -> core::result::Result<$wrapper, bitcoin::consensus::encode::Error> { + let len = r.read_compact_size()?; + // Do not allocate upfront more items than if the sequence of type + // occupied roughly quarter a block. This should never be the case + // for normal data, but even if that's not true - `push` will just + // reallocate. + // Note: OOM protection relies on reader eventually running out of + // data to feed us. + let max_capacity = + bitcoin::consensus::encode::MAX_VEC_SIZE / 4 / core::mem::size_of::<$type>(); + let mut ret = Vec::with_capacity(core::cmp::min(len as usize, max_capacity)); + for _ in 0..len { + ret.push(Decodable::consensus_decode_from_finite_reader(r)?); + } + Ok($wrapper(ret)) + } + } + }; +} + +pub(crate) use impl_vec_wrapper; diff --git a/p2p/src/lib.rs b/p2p/src/lib.rs index bb9afc7d9..325026318 100644 --- a/p2p/src/lib.rs +++ b/p2p/src/lib.rs @@ -14,3 +14,495 @@ #![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134 #![allow(clippy::manual_range_contains)] // More readable than clippy's format. #![allow(clippy::uninlined_format_args)] // Allow `format!("{}", x)`instead of enforcing `format!("{x}")` + +pub mod address; +pub mod message; +pub mod message_blockdata; +pub mod message_bloom; +pub mod message_compact_blocks; +pub mod message_filter; +pub mod message_network; +mod consensus; + +extern crate alloc; + +use core::str::FromStr; +use core::{fmt, ops}; + +use std::borrow::{Borrow, BorrowMut, ToOwned}; + +use hex::FromHex; +use internals::impl_to_hex_from_lower_hex; +use io::{BufRead, Write}; + +use bitcoin::consensus::encode::{self, Decodable, Encodable}; +use bitcoin::network::{Network, Params, TestnetVersion}; + +#[rustfmt::skip] +#[doc(inline)] +pub use self::address::Address; + +/// 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 +/// 70014 - Support compact block messages `sendcmpct`, `cmpctblock`, `getblocktxn` and `blocktxn` +/// 70013 - Support `feefilter` message +/// 70012 - Support `sendheaders` message and announce new blocks via headers rather than inv +/// 70011 - Support NODE_BLOOM service flag and don't support bloom filter messages if it is not set +/// 70002 - Support `reject` message +/// 70001 - Support bloom filter messages `filterload`, `filterclear` `filteradd`, `merkleblock` and FILTERED_BLOCK inventory type +/// 60002 - Support `mempool` message +/// 60001 - Support `pong` message and nonce in `ping` message +pub const PROTOCOL_VERSION: u32 = 70001; + +/// Flags to indicate which network services a node supports. +#[derive(Debug, Clone, Copy, Default, 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); + + /// COMPACT_FILTERS means the node will service basic block filter requests. + /// See BIP157 and BIP158 for details on how this is implemented. + pub const COMPACT_FILTERS: ServiceFlags = ServiceFlags(1 << 6); + + /// 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); + + /// P2P_V2 indicates that the node supports the P2P v2 encrypted transport protocol. + /// See BIP324 for details on how this is implemented. + pub const P2P_V2: ServiceFlags = ServiceFlags(1 << 11); + + // NOTE: When adding new flags, remember to update the Display impl accordingly. + + /// Add [ServiceFlags] together. + /// + /// Returns itself. + #[must_use] + pub fn add(&mut self, other: ServiceFlags) -> ServiceFlags { + self.0 |= other.0; + *self + } + + /// Remove [ServiceFlags] from this. + /// + /// Returns itself. + #[must_use] + 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 } + + /// Gets the integer representation of this [`ServiceFlags`]. + pub fn to_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_to_hex_from_lower_hex!(ServiceFlags, |service_flags: &ServiceFlags| 16 + - service_flags.0.leading_zeros() as usize / 4); + +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 { + let mut flags = *self; + if flags == ServiceFlags::NONE { + return write!(f, "ServiceFlags(NONE)"); + } + 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))?; + let _ = flags.remove(ServiceFlags::$f); + } + }; + } + write!(f, "ServiceFlags(")?; + write_flag!(NETWORK); + write_flag!(GETUTXO); + write_flag!(BLOOM); + write_flag!(WITNESS); + write_flag!(COMPACT_FILTERS); + write_flag!(NETWORK_LIMITED); + write_flag!(P2P_V2); + // 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 From for u64 { + fn from(flags: ServiceFlags) -> Self { flags.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) { let _ = 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) { let _ = self.remove(rhs); } +} + +impl Encodable for ServiceFlags { + #[inline] + fn consensus_encode(&self, w: &mut W) -> Result { + self.0.consensus_encode(w) + } +} + +impl Decodable for ServiceFlags { + #[inline] + fn consensus_decode(r: &mut R) -> Result { + 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 testnet3 network magic bytes. + #[deprecated(since = "0.33.0", note = "use `TESTNET3` instead")] + pub const TESTNET: Self = Self([0x0B, 0x11, 0x09, 0x07]); + /// Bitcoin testnet3 network magic bytes. + pub const TESTNET3: Self = Self([0x0B, 0x11, 0x09, 0x07]); + /// Bitcoin testnet4 network magic bytes. + pub const TESTNET4: Self = Self([0x1c, 0x16, 0x3f, 0x28]); + /// 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]); + + /// Construct a new network magic from bytes. + pub const fn from_bytes(bytes: [u8; 4]) -> Magic { Magic(bytes) } + + /// Get network magic bytes. + pub fn to_bytes(self) -> [u8; 4] { self.0 } + + /// Returns the magic bytes for the network defined by `params`. + pub fn from_params(params: impl AsRef) -> Option { params.as_ref().network.try_into().ok() } +} + +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 TryFrom for Magic { + type Error = UnknownNetworkError; + + fn try_from(network: Network) -> Result { + match network { + Network::Bitcoin => Ok(Magic::BITCOIN), + Network::Testnet(TestnetVersion::V3) => Ok(Magic::TESTNET3), + Network::Testnet(TestnetVersion::V4) => Ok(Magic::TESTNET4), + Network::Signet => Ok(Magic::SIGNET), + Network::Regtest => Ok(Magic::REGTEST), + _ => Err(UnknownNetworkError(network)), + } + } +} + +impl TryFrom for Network { + type Error = UnknownMagicError; + + fn try_from(magic: Magic) -> Result { + match magic { + Magic::BITCOIN => Ok(Network::Bitcoin), + Magic::TESTNET3 => Ok(Network::Testnet(TestnetVersion::V3)), + Magic::TESTNET4 => Ok(Network::Testnet(TestnetVersion::V4)), + Magic::SIGNET => Ok(Network::Signet), + Magic::REGTEST => Ok(Network::Regtest), + _ => Err(UnknownMagicError(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(()) + } +} + +impl fmt::Debug for Magic { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { fmt::Display::fmt(self, f) } +} + +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_to_hex_from_lower_hex!(Magic, |_| 8); + +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 } +} + +/// An error in parsing magic bytes. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub struct ParseMagicError { + /// The error that occurred when parsing the string. + error: hex::HexToArrayError, + /// The byte string that failed to parse. + magic: String, +} + +impl fmt::Display for ParseMagicError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "failed to parse {} as network magic", self.magic) + } +} + +impl std::error::Error for ParseMagicError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.error) } +} + +/// Error in creating a Network from Magic bytes. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub struct UnknownMagicError(Magic); + +impl fmt::Display for UnknownMagicError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "unknown network magic {}", self.0) + } +} + +impl std::error::Error for UnknownMagicError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } +} + +/// Error in creating a Magic from a Network. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub struct UnknownNetworkError(Network); + +impl fmt::Display for UnknownNetworkError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "unknown network {}", self.0) + } +} + +impl std::error::Error for UnknownNetworkError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } +} + +#[cfg(test)] +mod tests { + use super::*; + use bitcoin::consensus::encode::{deserialize, serialize}; + + #[test] + fn serialize_deserialize() { + assert_eq!(serialize(&Magic::BITCOIN), &[0xf9, 0xbe, 0xb4, 0xd9]); + let magic: Magic = Network::Bitcoin.try_into().unwrap(); + assert_eq!(serialize(&magic), &[0xf9, 0xbe, 0xb4, 0xd9]); + assert_eq!(serialize(&Magic::TESTNET3), &[0x0b, 0x11, 0x09, 0x07]); + let magic: Magic = Network::Testnet(TestnetVersion::V3).try_into().unwrap(); + assert_eq!(serialize(&magic), &[0x0b, 0x11, 0x09, 0x07]); + assert_eq!(serialize(&Magic::TESTNET4), &[0x1c, 0x16, 0x3f, 0x28]); + let magic: Magic = Network::Testnet(TestnetVersion::V4).try_into().unwrap(); + assert_eq!(serialize(&magic), &[0x1c, 0x16, 0x3f, 0x28]); + assert_eq!(serialize(&Magic::SIGNET), &[0x0a, 0x03, 0xcf, 0x40]); + let magic: Magic = Network::Signet.try_into().unwrap(); + assert_eq!(serialize(&magic), &[0x0a, 0x03, 0xcf, 0x40]); + assert_eq!(serialize(&Magic::REGTEST), &[0xfa, 0xbf, 0xb5, 0xda]); + let magic: Magic = Network::Regtest.try_into().unwrap(); + assert_eq!(serialize(&magic), &[0xfa, 0xbf, 0xb5, 0xda]); + + assert_eq!(deserialize::(&[0xf9, 0xbe, 0xb4, 0xd9]).ok(), Network::Bitcoin.try_into().ok()); + assert_eq!( + deserialize::(&[0x0b, 0x11, 0x09, 0x07]).ok(), + Network::Testnet(TestnetVersion::V3).try_into().ok() + ); + assert_eq!( + deserialize::(&[0x1c, 0x16, 0x3f, 0x28]).ok(), + Network::Testnet(TestnetVersion::V4).try_into().ok() + ); + assert_eq!(deserialize::(&[0x0a, 0x03, 0xcf, 0x40]).ok(), Network::Signet.try_into().ok()); + assert_eq!(deserialize::(&[0xfa, 0xbf, 0xb5, 0xda]).ok(), Network::Regtest.try_into().ok()); + } + + + #[test] + fn service_flags_test() { + let all = [ + ServiceFlags::NETWORK, + ServiceFlags::GETUTXO, + ServiceFlags::BLOOM, + ServiceFlags::WITNESS, + ServiceFlags::COMPACT_FILTERS, + ServiceFlags::NETWORK_LIMITED, + ServiceFlags::P2P_V2, + ]; + + 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()); + assert_eq!("ServiceFlags(P2P_V2)", ServiceFlags::P2P_V2.to_string()); + let flag = ServiceFlags::WITNESS + | ServiceFlags::BLOOM + | ServiceFlags::NETWORK + | ServiceFlags::P2P_V2; + assert_eq!("ServiceFlags(NETWORK|BLOOM|WITNESS|P2P_V2)", flag.to_string()); + let flag = ServiceFlags::WITNESS | 0xf0.into(); + assert_eq!("ServiceFlags(WITNESS|COMPACT_FILTERS|0xb0)", flag.to_string()); + } + + #[test] + fn magic_from_str() { + let known_network_magic_strs = [ + ("f9beb4d9", Network::Bitcoin), + ("0b110907", Network::Testnet(TestnetVersion::V3)), + ("1c163f28", Network::Testnet(TestnetVersion::V4)), + ("fabfb5da", Network::Regtest), + ("0a03cf40", Network::Signet), + ]; + + for (magic_str, network) in &known_network_magic_strs { + let magic: Magic = magic_str.parse::().unwrap(); + assert_eq!(Network::try_from(magic).unwrap(), *network); + assert_eq!(&magic.to_string(), magic_str); + } + } +} diff --git a/bitcoin/src/p2p/message.rs b/p2p/src/message.rs similarity index 96% rename from bitcoin/src/p2p/message.rs rename to p2p/src/message.rs index 14b11f27d..c83bfde91 100644 --- a/bitcoin/src/p2p/message.rs +++ b/p2p/src/message.rs @@ -6,21 +6,23 @@ //! are used for (de)serializing Bitcoin objects for transmission on the network. use core::{fmt, iter}; +use std::borrow::Cow; +use std::boxed::Box; +use std::borrow::ToOwned; use hashes::sha256d; use internals::ToU64 as _; use io::{BufRead, Write}; -use crate::consensus::encode::{self, CheckedData, Decodable, Encodable, ReadExt, WriteExt}; -use crate::merkle_tree::MerkleBlock; -use crate::p2p::address::{AddrV2Message, Address}; -use crate::p2p::deser::impl_vec_wrapper; -use crate::p2p::{ +use bitcoin::consensus::encode::{self, CheckedData, Decodable, Encodable, ReadExt, WriteExt}; +use bitcoin::merkle_tree::MerkleBlock; +use crate::address::{AddrV2Message, Address}; +use crate::consensus::impl_vec_wrapper; +use crate::{ message_blockdata, message_bloom, message_compact_blocks, message_filter, message_network, Magic, }; -use crate::prelude::{Box, Cow, String, ToOwned, Vec}; -use crate::{block, consensus, transaction}; +use bitcoin::{block, transaction}; /// The maximum number of [super::message_blockdata::Inventory] items in an `inv` message. /// @@ -144,7 +146,6 @@ impl fmt::Display for CommandStringError { } } -#[cfg(feature = "std")] impl std::error::Error for CommandStringError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } } @@ -518,7 +519,7 @@ impl Decodable for HeaderDeserializationWrapper { for _ in 0..len { ret.push(Decodable::consensus_decode(r)?); if u8::consensus_decode(r)? != 0u8 { - return Err(consensus::parse_failed_error( + return Err(crate::consensus::parse_failed_error( "Headers message should not contain transactions", )); } @@ -718,21 +719,21 @@ mod test { use units::BlockHeight; use super::*; - use crate::bip152::BlockTransactionsRequest; - use crate::bip158::{FilterHash, FilterHeader}; - use crate::block::{Block, BlockHash}; - use crate::consensus::encode::{deserialize, deserialize_partial, serialize}; - use crate::p2p::address::AddrV2; - 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::{ + use bitcoin::bip152::BlockTransactionsRequest; + use bitcoin::bip158::{FilterHash, FilterHeader}; + use bitcoin::block::{Block, BlockHash}; + use bitcoin::consensus::encode::{deserialize, deserialize_partial, serialize}; + use bitcoin::script::ScriptBuf; + use bitcoin::transaction::{Transaction, Txid}; + use crate::address::AddrV2; + use crate::message_blockdata::{GetBlocksMessage, GetHeadersMessage, Inventory}; + use crate::message_bloom::{BloomFlags, FilterAdd, FilterLoad}; + use crate::message_compact_blocks::{GetBlockTxn, SendCmpct}; + use crate::message_filter::{ CFCheckpt, CFHeaders, CFilter, GetCFCheckpt, GetCFHeaders, GetCFilters, }; - use crate::p2p::message_network::{Reject, RejectReason, VersionMessage}; - use crate::p2p::ServiceFlags; - use crate::script::ScriptBuf; - use crate::transaction::{Transaction, Txid}; + use crate::message_network::{Reject, RejectReason, VersionMessage}; + use crate::ServiceFlags; fn hash(array: [u8; 32]) -> sha256d::Hash { sha256d::Hash::from_byte_array(array) } @@ -740,7 +741,7 @@ mod test { fn full_round_ser_der_raw_network_message() { let version_msg: VersionMessage = deserialize(&hex!("721101000100000000000000e6e0845300000000010000000000000000000000000000000000ffff0000000000000100000000000000fd87d87eeb4364f22cf54dca59412db7208d47d920cffce83ee8102f5361746f7368693a302e392e39392f2c9f040001")).unwrap(); let tx: Transaction = deserialize(&hex!("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000")).unwrap(); - let block: Block = deserialize(&include_bytes!("../../tests/data/testnet_block_000000000000045e0b1660b6445b5e5c5ab63c9a4f956be7e1e69be04fa4497b.raw")[..]).unwrap(); + let block: Block = deserialize(&hex!("00608e2e094d41aecfbcbf8fe70cb60be57516b07db1bafee4c4de5dad760000000000004aec16eab3be95abe9c54e01cf850c14b8c5cad1bc6b2e73e811db5d5998ada404503e66fcff031b4ebd99d701010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff3402983a000404503e6604f1f617271083bc3d6600000000000000000007bb1b0a636b706f6f6c0d506f72746c616e642e484f444cffffffff0200f2052a010000001976a9142ce72b25fe97b52638c199acfaa5e3891ddfed5b88ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000")).unwrap(); let header: block::Header = deserialize(&hex!("010000004ddccd549d28f385ab457e98d1b11ce80bfea2c5ab93015ade4973e400000000bf4473e53794beae34e64fccc471dace6ae544180816f89591894e0f417a914cd74d6e49ffff001d323b3a7b")).unwrap(); let script: ScriptBuf = deserialize(&hex!("1976a91431a420903c05a0a7de2de40c9f02ebedbacdc17288ac")).unwrap(); diff --git a/bitcoin/src/p2p/message_blockdata.rs b/p2p/src/message_blockdata.rs similarity index 93% rename from bitcoin/src/p2p/message_blockdata.rs rename to p2p/src/message_blockdata.rs index 3d9231204..e2c388af7 100644 --- a/bitcoin/src/p2p/message_blockdata.rs +++ b/p2p/src/message_blockdata.rs @@ -7,11 +7,10 @@ use io::{BufRead, Write}; -use crate::block::BlockHash; -use crate::consensus::encode::{self, Decodable, Encodable}; -use crate::internal_macros::impl_consensus_encoding; -use crate::p2p; -use crate::transaction::{Txid, Wtxid}; +use bitcoin::block::BlockHash; +use bitcoin::consensus::encode::{self, Decodable, Encodable}; +use bitcoin::transaction::{Txid, Wtxid}; +use crate::consensus::impl_consensus_encoding; /// An inventory item. #[derive(PartialEq, Eq, Clone, Debug, Copy, Hash, PartialOrd, Ord)] @@ -127,7 +126,7 @@ pub struct GetHeadersMessage { impl GetBlocksMessage { /// Construct a new `getblocks` message pub fn new(locator_hashes: Vec, stop_hash: BlockHash) -> GetBlocksMessage { - GetBlocksMessage { version: p2p::PROTOCOL_VERSION, locator_hashes, stop_hash } + GetBlocksMessage { version: crate::PROTOCOL_VERSION, locator_hashes, stop_hash } } } @@ -136,7 +135,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: p2p::PROTOCOL_VERSION, locator_hashes, stop_hash } + GetHeadersMessage { version: crate::PROTOCOL_VERSION, locator_hashes, stop_hash } } } @@ -147,7 +146,7 @@ mod tests { use hex_lit::hex; use super::*; - use crate::consensus::encode::{deserialize, serialize}; + use bitcoin::consensus::encode::{deserialize, serialize}; #[test] fn getblocks_message() { diff --git a/bitcoin/src/p2p/message_bloom.rs b/p2p/src/message_bloom.rs similarity index 89% rename from bitcoin/src/p2p/message_bloom.rs rename to p2p/src/message_bloom.rs index 2a75f9a64..69f079da2 100644 --- a/bitcoin/src/p2p/message_bloom.rs +++ b/p2p/src/message_bloom.rs @@ -6,8 +6,8 @@ use io::{BufRead, Write}; -use crate::consensus::{self, encode, Decodable, Encodable, ReadExt}; -use crate::internal_macros::impl_consensus_encoding; +use bitcoin::consensus::{encode, Decodable, Encodable, ReadExt}; +use crate::consensus::impl_consensus_encoding; /// `filterload` message sets the current bloom filter #[derive(Clone, PartialEq, Eq, Debug)] @@ -52,7 +52,7 @@ impl Decodable for BloomFlags { 0 => BloomFlags::None, 1 => BloomFlags::All, 2 => BloomFlags::PubkeyOnly, - _ => return Err(consensus::parse_failed_error("unknown bloom flag")), + _ => return Err(crate::consensus::parse_failed_error("unknown bloom flag")), }) } } diff --git a/bitcoin/src/p2p/message_compact_blocks.rs b/p2p/src/message_compact_blocks.rs similarity index 95% rename from bitcoin/src/p2p/message_compact_blocks.rs rename to p2p/src/message_compact_blocks.rs index 333a8c243..9b6735c0d 100644 --- a/bitcoin/src/p2p/message_compact_blocks.rs +++ b/p2p/src/message_compact_blocks.rs @@ -3,8 +3,8 @@ //! //! BIP152 Compact Blocks network messages -use crate::bip152; -use crate::internal_macros::impl_consensus_encoding; +use bitcoin::bip152; +use crate::consensus::impl_consensus_encoding; /// sendcmpct message #[derive(PartialEq, Eq, Clone, Debug, Copy, PartialOrd, Ord, Hash)] diff --git a/bitcoin/src/p2p/message_filter.rs b/p2p/src/message_filter.rs similarity index 95% rename from bitcoin/src/p2p/message_filter.rs rename to p2p/src/message_filter.rs index 344e2d0a8..65c9c3d7c 100644 --- a/bitcoin/src/p2p/message_filter.rs +++ b/p2p/src/message_filter.rs @@ -6,9 +6,9 @@ use units::BlockHeight; -use crate::bip158::{FilterHash, FilterHeader}; -use crate::block::BlockHash; -use crate::internal_macros::impl_consensus_encoding; +use bitcoin::bip158::{FilterHash, FilterHeader}; +use bitcoin::block::BlockHash; +use crate::consensus::impl_consensus_encoding; /// getcfilters message #[derive(PartialEq, Eq, Clone, Debug)] diff --git a/bitcoin/src/p2p/message_network.rs b/p2p/src/message_network.rs similarity index 94% rename from bitcoin/src/p2p/message_network.rs rename to p2p/src/message_network.rs index d4a41b9e1..258221a0c 100644 --- a/bitcoin/src/p2p/message_network.rs +++ b/p2p/src/message_network.rs @@ -4,16 +4,15 @@ //! //! This module defines network messages which describe peers and their //! capabilities. +use std::borrow::Cow; use hashes::sha256d; use io::{BufRead, Write}; -use crate::consensus::{self, encode, Decodable, Encodable, ReadExt}; -use crate::internal_macros::impl_consensus_encoding; -use crate::p2p; -use crate::p2p::address::Address; -use crate::p2p::ServiceFlags; -use crate::prelude::{Cow, String}; +use bitcoin::consensus::{encode, Decodable, Encodable, ReadExt}; +use crate::address::Address; +use crate::consensus::impl_consensus_encoding; +use crate::ServiceFlags; // Some simple messages @@ -61,7 +60,7 @@ impl VersionMessage { start_height: i32, ) -> VersionMessage { VersionMessage { - version: p2p::PROTOCOL_VERSION, + version: crate::PROTOCOL_VERSION, services, timestamp, receiver, @@ -126,7 +125,7 @@ impl Decodable for RejectReason { 0x41 => RejectReason::Dust, 0x42 => RejectReason::Fee, 0x43 => RejectReason::Checkpoint, - _ => return Err(consensus::parse_failed_error("unknown reject code")), + _ => return Err(crate::consensus::parse_failed_error("unknown reject code")), }) } } @@ -151,7 +150,7 @@ mod tests { use hex_lit::hex; use super::*; - use crate::consensus::encode::{deserialize, serialize}; + use bitcoin::consensus::encode::{deserialize, serialize}; #[test] fn version_message_test() {