Move `bitcoin/p2p` into `p2p`

Moves all of the content from `bitcoin/p2p` into `p2p`.

`TryFrom<Network>` must be implemented for `Network -> Magic` now that
`Network` is a foreign, non-exhaustive type. Ser/de test is updated
accordingly, as well as the `Magic::from_network` constructor, which I
have opted to return an `Option<Self>`. The `TryFrom` implementation
uses a new `UnknownNetworkError(Network)` that mirrors the `Magic ->
Network` error.

The example handshake does not generate a random nonce for the version
message, as there is no `rand` dependency in this crate and the program
only makes a single, user-defined connection.

It appears we can do better than copying and pasting the consensus
encoding macros and functions from `bitcoin` without doing weird
cross-crate macros that require some special knowledge of the crate to
know when they will compile.
This commit is contained in:
rustaceanrob 2025-06-02 15:34:43 +01:00
parent 9852732311
commit d9cf7270eb
No known key found for this signature in database
GPG Key ID: F4DD8F8486EC0F1F
20 changed files with 687 additions and 613 deletions

View File

@ -77,6 +77,7 @@ name = "bitcoin-fuzz"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"bitcoin", "bitcoin",
"bitcoin-p2p-messages",
"honggfuzz", "honggfuzz",
"serde", "serde",
"serde_json", "serde_json",
@ -109,6 +110,15 @@ dependencies = [
[[package]] [[package]]
name = "bitcoin-p2p-messages" name = "bitcoin-p2p-messages"
version = "0.1.0" 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]] [[package]]
name = "bitcoin-primitives" name = "bitcoin-primitives"

View File

@ -76,6 +76,7 @@ name = "bitcoin-fuzz"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"bitcoin", "bitcoin",
"bitcoin-p2p-messages",
"honggfuzz", "honggfuzz",
"serde", "serde",
"serde_json", "serde_json",
@ -108,6 +109,15 @@ dependencies = [
[[package]] [[package]]
name = "bitcoin-p2p-messages" name = "bitcoin-p2p-messages"
version = "0.1.0" 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]] [[package]]
name = "bitcoin-primitives" name = "bitcoin-primitives"

View File

@ -56,10 +56,6 @@ rustdoc-args = ["--cfg", "docsrs"]
[[example]] [[example]]
name = "bip32" name = "bip32"
[[example]]
name = "handshake"
required-features = ["rand-std"]
[[example]] [[example]]
name = "ecdsa-psbt" name = "ecdsa-psbt"
required-features = ["std", "bitcoinconsensus"] required-features = ["std", "bitcoinconsensus"]

View File

@ -94,7 +94,6 @@ extern crate serde;
mod internal_macros; mod internal_macros;
#[macro_use] #[macro_use]
pub mod p2p;
pub mod address; pub mod address;
pub mod bip152; pub mod bip152;
pub mod bip158; pub mod bip158;

View File

@ -1,42 +0,0 @@
macro_rules! impl_vec_wrapper {
($wrapper: ident, $type: ty) => {
impl crate::consensus::encode::Encodable for $wrapper {
#[inline]
fn consensus_encode<W: io::Write + ?Sized>(
&self,
w: &mut W,
) -> core::result::Result<usize, io::Error> {
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: io::BufRead + ?Sized>(
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;

View File

@ -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<u64> for ServiceFlags {
fn from(f: u64) -> Self { ServiceFlags(f) }
}
impl From<ServiceFlags> 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<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
self.0.consensus_encode(w)
}
}
impl Decodable for ServiceFlags {
#[inline]
fn consensus_decode<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
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<Params>) -> Self { params.as_ref().network.into() }
}
impl FromStr for Magic {
type Err = ParseMagicError;
fn from_str(s: &str) -> Result<Magic, Self::Err> {
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<Network> 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<Magic>` for new networks
}
}
}
impl TryFrom<Magic> for Network {
type Error = UnknownMagicError;
fn try_from(magic: Magic) -> Result<Self, Self::Error> {
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<W: Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
self.0.consensus_encode(writer)
}
}
impl Decodable for Magic {
fn consensus_decode<R: BufRead + ?Sized>(reader: &mut R) -> Result<Self, encode::Error> {
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::<Magic>(&[0xf9, 0xbe, 0xb4, 0xd9]).ok(),
Some(Network::Bitcoin.into())
);
assert_eq!(
deserialize::<Magic>(&[0x0b, 0x11, 0x09, 0x07]).ok(),
Some(Network::Testnet(TestnetVersion::V3).into())
);
assert_eq!(
deserialize::<Magic>(&[0x1c, 0x16, 0x3f, 0x28]).ok(),
Some(Network::Testnet(TestnetVersion::V4).into())
);
assert_eq!(
deserialize::<Magic>(&[0x0a, 0x03, 0xcf, 0x40]).ok(),
Some(Network::Signet.into())
);
assert_eq!(
deserialize::<Magic>(&[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::<Magic>().unwrap();
assert_eq!(Network::try_from(magic).unwrap(), *network);
assert_eq!(&magic.to_string(), magic_str);
}
}
}

View File

@ -11,6 +11,7 @@ cargo-fuzz = true
[dependencies] [dependencies]
honggfuzz = { version = "0.5.56", default-features = false } honggfuzz = { version = "0.5.56", default-features = false }
bitcoin = { path = "../bitcoin", features = [ "serde" ] } bitcoin = { path = "../bitcoin", features = [ "serde" ] }
p2p = { path = "../p2p", package = "bitcoin-p2p-messages" }
serde = { version = "1.0.103", features = [ "derive" ] } serde = { version = "1.0.103", features = [ "derive" ] }
serde_json = "1.0" serde_json = "1.0"

View File

@ -1,7 +1,7 @@
use honggfuzz::fuzz; use honggfuzz::fuzz;
fn do_test(data: &[u8]) { fn do_test(data: &[u8]) {
let _: Result<bitcoin::p2p::message::RawNetworkMessage, _> = let _: Result<p2p::message::RawNetworkMessage, _> =
bitcoin::consensus::encode::deserialize(data); bitcoin::consensus::encode::deserialize(data);
} }

View File

@ -2,7 +2,7 @@ use std::convert::TryFrom;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use bitcoin::consensus::Decodable; use bitcoin::consensus::Decodable;
use bitcoin::p2p::address::AddrV2; use p2p::address::AddrV2;
use honggfuzz::fuzz; use honggfuzz::fuzz;
fn do_test(data: &[u8]) { fn do_test(data: &[u8]) {

View File

@ -13,8 +13,18 @@ rust-version = "1.63.0"
exclude = ["tests", "contrib"] exclude = ["tests", "contrib"]
[dependencies] [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] [dev-dependencies]
hex_lit = "0.1.1"
[[example]]
name = "handshake"
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View File

@ -4,8 +4,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
use std::{env, process}; use std::{env, process};
use bitcoin::consensus::{encode, Decodable}; use bitcoin::consensus::{encode, Decodable};
use bitcoin::p2p::{self, address, message, message_network, Magic}; use bitcoin_p2p_messages::{self, address, message, message_network, Magic, ServiceFlags};
use bitcoin::secp256k1::rand::Rng;
fn main() { fn main() {
// This example establishes a connection to a Bitcoin node, sends the initial // 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); let my_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0);
// "bitfield of features to be enabled for this connection" // "bitfield of features to be enabled for this connection"
let services = p2p::ServiceFlags::NONE; let services = ServiceFlags::NONE;
// "standard UNIX timestamp in seconds" // "standard UNIX timestamp in seconds"
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time error").as_secs(); let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time error").as_secs();
// "The network address of the node receiving this message" // "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" // "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." // "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)" // "User Agent (0x00 if string is 0 bytes long)"
let user_agent = String::from("rust-example"); let user_agent = String::from("rust-example");

View File

@ -10,9 +10,8 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV
use io::{BufRead, Read, Write}; use io::{BufRead, Read, Write};
use crate::consensus; use bitcoin::consensus::encode::{self, Decodable, Encodable, ReadExt, WriteExt};
use crate::consensus::encode::{self, Decodable, Encodable, ReadExt, WriteExt}; use crate::ServiceFlags;
use crate::p2p::ServiceFlags;
/// A message which can be sent on the Bitcoin network /// A message which can be sent on the Bitcoin network
#[derive(Clone, PartialEq, Eq, Hash)] #[derive(Clone, PartialEq, Eq, Hash)]
@ -207,7 +206,7 @@ impl Encodable for AddrV2 {
network: u8, network: u8,
bytes: &[u8], bytes: &[u8],
) -> Result<usize, io::Error> { ) -> Result<usize, io::Error> {
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 { Ok(match *self {
AddrV2::Ipv4(ref addr) => encode_addr(w, 1, &addr.octets())?, 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 network_id = u8::consensus_decode(r)?;
let len = r.read_compact_size()?; let len = r.read_compact_size()?;
if len > 512 { 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 { Ok(match network_id {
1 => { 1 => {
if len != 4 { 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)?; let addr: [u8; 4] = Decodable::consensus_decode(r)?;
AddrV2::Ipv4(Ipv4Addr::new(addr[0], addr[1], addr[2], addr[3])) AddrV2::Ipv4(Ipv4Addr::new(addr[0], addr[1], addr[2], addr[3]))
} }
2 => { 2 => {
if len != 16 { 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)?; let addr: [u16; 8] = read_be_address(r)?;
if addr[0..3] == ONION { 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", "OnionCat address sent with IPv6 network id",
)); ));
} }
if addr[0..6] == [0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF] { 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", "IPV4 wrapped address sent with IPv6 network id",
)); ));
} }
@ -257,26 +256,26 @@ impl Decodable for AddrV2 {
4 => { 4 => {
if len != 32 { 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)?; let pubkey = Decodable::consensus_decode(r)?;
AddrV2::TorV3(pubkey) AddrV2::TorV3(pubkey)
} }
5 => { 5 => {
if len != 32 { 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)?; let hash = Decodable::consensus_decode(r)?;
AddrV2::I2p(hash) AddrV2::I2p(hash)
} }
6 => { 6 => {
if len != 16 { 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)?; let addr: [u16; 8] = read_be_address(r)?;
// check the first byte for the CJDNS marker // check the first byte for the CJDNS marker
if addr[0] >> 8 != 0xFC { 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( AddrV2::Cjdns(Ipv6Addr::new(
addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7], 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 { mod test {
use std::net::IpAddr; use std::net::IpAddr;
use bitcoin::consensus::encode::{deserialize, serialize};
use hex::FromHex; use hex::FromHex;
use hex_lit::hex; use hex_lit::hex;
use super::*; use super::*;
use crate::consensus::encode::{deserialize, serialize}; use crate::message::AddrV2Payload;
use crate::p2p::message::AddrV2Payload;
#[test] #[test]
fn serialize_address() { fn serialize_address() {

95
p2p/src/consensus.rs Normal file
View File

@ -0,0 +1,95 @@
use bitcoin::consensus::encode::WriteExt;
use io::Write;
pub(crate) fn consensus_encode_with_size<W: Write + ?Sized>(
data: &[u8],
w: &mut W,
) -> Result<usize, io::Error> {
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<W: io::Write + ?Sized>(
&self,
w: &mut W,
) -> core::result::Result<usize, io::Error> {
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: io::BufRead + ?Sized>(
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: io::BufRead + ?Sized>(
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<W: io::Write + ?Sized>(
&self,
w: &mut W,
) -> core::result::Result<usize, io::Error> {
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: io::BufRead + ?Sized>(
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;

View File

@ -14,3 +14,495 @@
#![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134 #![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::manual_range_contains)] // More readable than clippy's format.
#![allow(clippy::uninlined_format_args)] // Allow `format!("{}", x)`instead of enforcing `format!("{x}")` #![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<u64> for ServiceFlags {
fn from(f: u64) -> Self { ServiceFlags(f) }
}
impl From<ServiceFlags> 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<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
self.0.consensus_encode(w)
}
}
impl Decodable for ServiceFlags {
#[inline]
fn consensus_decode<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
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<Params>) -> Option<Self> { params.as_ref().network.try_into().ok() }
}
impl FromStr for Magic {
type Err = ParseMagicError;
fn from_str(s: &str) -> Result<Magic, Self::Err> {
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<Network> for Magic {
type Error = UnknownNetworkError;
fn try_from(network: Network) -> Result<Self, Self::Error> {
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<Magic> for Network {
type Error = UnknownMagicError;
fn try_from(magic: Magic) -> Result<Self, Self::Error> {
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<W: Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
self.0.consensus_encode(writer)
}
}
impl Decodable for Magic {
fn consensus_decode<R: BufRead + ?Sized>(reader: &mut R) -> Result<Self, encode::Error> {
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::<Magic>(&[0xf9, 0xbe, 0xb4, 0xd9]).ok(), Network::Bitcoin.try_into().ok());
assert_eq!(
deserialize::<Magic>(&[0x0b, 0x11, 0x09, 0x07]).ok(),
Network::Testnet(TestnetVersion::V3).try_into().ok()
);
assert_eq!(
deserialize::<Magic>(&[0x1c, 0x16, 0x3f, 0x28]).ok(),
Network::Testnet(TestnetVersion::V4).try_into().ok()
);
assert_eq!(deserialize::<Magic>(&[0x0a, 0x03, 0xcf, 0x40]).ok(), Network::Signet.try_into().ok());
assert_eq!(deserialize::<Magic>(&[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::<Magic>().unwrap();
assert_eq!(Network::try_from(magic).unwrap(), *network);
assert_eq!(&magic.to_string(), magic_str);
}
}
}

View File

@ -6,21 +6,23 @@
//! are used for (de)serializing Bitcoin objects for transmission on the network. //! are used for (de)serializing Bitcoin objects for transmission on the network.
use core::{fmt, iter}; use core::{fmt, iter};
use std::borrow::Cow;
use std::boxed::Box;
use std::borrow::ToOwned;
use hashes::sha256d; use hashes::sha256d;
use internals::ToU64 as _; use internals::ToU64 as _;
use io::{BufRead, Write}; use io::{BufRead, Write};
use crate::consensus::encode::{self, CheckedData, Decodable, Encodable, ReadExt, WriteExt}; use bitcoin::consensus::encode::{self, CheckedData, Decodable, Encodable, ReadExt, WriteExt};
use crate::merkle_tree::MerkleBlock; use bitcoin::merkle_tree::MerkleBlock;
use crate::p2p::address::{AddrV2Message, Address}; use crate::address::{AddrV2Message, Address};
use crate::p2p::deser::impl_vec_wrapper; use crate::consensus::impl_vec_wrapper;
use crate::p2p::{ use crate::{
message_blockdata, message_bloom, message_compact_blocks, message_filter, message_network, message_blockdata, message_bloom, message_compact_blocks, message_filter, message_network,
Magic, Magic,
}; };
use crate::prelude::{Box, Cow, String, ToOwned, Vec}; use bitcoin::{block, transaction};
use crate::{block, consensus, transaction};
/// The maximum number of [super::message_blockdata::Inventory] items in an `inv` message. /// 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 { impl std::error::Error for CommandStringError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
} }
@ -518,7 +519,7 @@ impl Decodable for HeaderDeserializationWrapper {
for _ in 0..len { for _ in 0..len {
ret.push(Decodable::consensus_decode(r)?); ret.push(Decodable::consensus_decode(r)?);
if u8::consensus_decode(r)? != 0u8 { 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", "Headers message should not contain transactions",
)); ));
} }
@ -718,21 +719,21 @@ mod test {
use units::BlockHeight; use units::BlockHeight;
use super::*; use super::*;
use crate::bip152::BlockTransactionsRequest; use bitcoin::bip152::BlockTransactionsRequest;
use crate::bip158::{FilterHash, FilterHeader}; use bitcoin::bip158::{FilterHash, FilterHeader};
use crate::block::{Block, BlockHash}; use bitcoin::block::{Block, BlockHash};
use crate::consensus::encode::{deserialize, deserialize_partial, serialize}; use bitcoin::consensus::encode::{deserialize, deserialize_partial, serialize};
use crate::p2p::address::AddrV2; use bitcoin::script::ScriptBuf;
use crate::p2p::message_blockdata::{GetBlocksMessage, GetHeadersMessage, Inventory}; use bitcoin::transaction::{Transaction, Txid};
use crate::p2p::message_bloom::{BloomFlags, FilterAdd, FilterLoad}; use crate::address::AddrV2;
use crate::p2p::message_compact_blocks::{GetBlockTxn, SendCmpct}; use crate::message_blockdata::{GetBlocksMessage, GetHeadersMessage, Inventory};
use crate::p2p::message_filter::{ use crate::message_bloom::{BloomFlags, FilterAdd, FilterLoad};
use crate::message_compact_blocks::{GetBlockTxn, SendCmpct};
use crate::message_filter::{
CFCheckpt, CFHeaders, CFilter, GetCFCheckpt, GetCFHeaders, GetCFilters, CFCheckpt, CFHeaders, CFilter, GetCFCheckpt, GetCFHeaders, GetCFilters,
}; };
use crate::p2p::message_network::{Reject, RejectReason, VersionMessage}; use crate::message_network::{Reject, RejectReason, VersionMessage};
use crate::p2p::ServiceFlags; use crate::ServiceFlags;
use crate::script::ScriptBuf;
use crate::transaction::{Transaction, Txid};
fn hash(array: [u8; 32]) -> sha256d::Hash { sha256d::Hash::from_byte_array(array) } 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() { fn full_round_ser_der_raw_network_message() {
let version_msg: VersionMessage = deserialize(&hex!("721101000100000000000000e6e0845300000000010000000000000000000000000000000000ffff0000000000000100000000000000fd87d87eeb4364f22cf54dca59412db7208d47d920cffce83ee8102f5361746f7368693a302e392e39392f2c9f040001")).unwrap(); let version_msg: VersionMessage = deserialize(&hex!("721101000100000000000000e6e0845300000000010000000000000000000000000000000000ffff0000000000000100000000000000fd87d87eeb4364f22cf54dca59412db7208d47d920cffce83ee8102f5361746f7368693a302e392e39392f2c9f040001")).unwrap();
let tx: Transaction = deserialize(&hex!("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000")).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 header: block::Header = deserialize(&hex!("010000004ddccd549d28f385ab457e98d1b11ce80bfea2c5ab93015ade4973e400000000bf4473e53794beae34e64fccc471dace6ae544180816f89591894e0f417a914cd74d6e49ffff001d323b3a7b")).unwrap();
let script: ScriptBuf = let script: ScriptBuf =
deserialize(&hex!("1976a91431a420903c05a0a7de2de40c9f02ebedbacdc17288ac")).unwrap(); deserialize(&hex!("1976a91431a420903c05a0a7de2de40c9f02ebedbacdc17288ac")).unwrap();

View File

@ -7,11 +7,10 @@
use io::{BufRead, Write}; use io::{BufRead, Write};
use crate::block::BlockHash; use bitcoin::block::BlockHash;
use crate::consensus::encode::{self, Decodable, Encodable}; use bitcoin::consensus::encode::{self, Decodable, Encodable};
use crate::internal_macros::impl_consensus_encoding; use bitcoin::transaction::{Txid, Wtxid};
use crate::p2p; use crate::consensus::impl_consensus_encoding;
use crate::transaction::{Txid, Wtxid};
/// An inventory item. /// An inventory item.
#[derive(PartialEq, Eq, Clone, Debug, Copy, Hash, PartialOrd, Ord)] #[derive(PartialEq, Eq, Clone, Debug, Copy, Hash, PartialOrd, Ord)]
@ -127,7 +126,7 @@ pub struct GetHeadersMessage {
impl GetBlocksMessage { impl GetBlocksMessage {
/// Construct a new `getblocks` message /// Construct a new `getblocks` message
pub fn new(locator_hashes: Vec<BlockHash>, stop_hash: BlockHash) -> GetBlocksMessage { pub fn new(locator_hashes: Vec<BlockHash>, 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 { impl GetHeadersMessage {
/// Construct a new `getheaders` message /// Construct a new `getheaders` message
pub fn new(locator_hashes: Vec<BlockHash>, stop_hash: BlockHash) -> GetHeadersMessage { pub fn new(locator_hashes: Vec<BlockHash>, 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 hex_lit::hex;
use super::*; use super::*;
use crate::consensus::encode::{deserialize, serialize}; use bitcoin::consensus::encode::{deserialize, serialize};
#[test] #[test]
fn getblocks_message() { fn getblocks_message() {

View File

@ -6,8 +6,8 @@
use io::{BufRead, Write}; use io::{BufRead, Write};
use crate::consensus::{self, encode, Decodable, Encodable, ReadExt}; use bitcoin::consensus::{encode, Decodable, Encodable, ReadExt};
use crate::internal_macros::impl_consensus_encoding; use crate::consensus::impl_consensus_encoding;
/// `filterload` message sets the current bloom filter /// `filterload` message sets the current bloom filter
#[derive(Clone, PartialEq, Eq, Debug)] #[derive(Clone, PartialEq, Eq, Debug)]
@ -52,7 +52,7 @@ impl Decodable for BloomFlags {
0 => BloomFlags::None, 0 => BloomFlags::None,
1 => BloomFlags::All, 1 => BloomFlags::All,
2 => BloomFlags::PubkeyOnly, 2 => BloomFlags::PubkeyOnly,
_ => return Err(consensus::parse_failed_error("unknown bloom flag")), _ => return Err(crate::consensus::parse_failed_error("unknown bloom flag")),
}) })
} }
} }

View File

@ -3,8 +3,8 @@
//! //!
//! BIP152 Compact Blocks network messages //! BIP152 Compact Blocks network messages
use crate::bip152; use bitcoin::bip152;
use crate::internal_macros::impl_consensus_encoding; use crate::consensus::impl_consensus_encoding;
/// sendcmpct message /// sendcmpct message
#[derive(PartialEq, Eq, Clone, Debug, Copy, PartialOrd, Ord, Hash)] #[derive(PartialEq, Eq, Clone, Debug, Copy, PartialOrd, Ord, Hash)]

View File

@ -6,9 +6,9 @@
use units::BlockHeight; use units::BlockHeight;
use crate::bip158::{FilterHash, FilterHeader}; use bitcoin::bip158::{FilterHash, FilterHeader};
use crate::block::BlockHash; use bitcoin::block::BlockHash;
use crate::internal_macros::impl_consensus_encoding; use crate::consensus::impl_consensus_encoding;
/// getcfilters message /// getcfilters message
#[derive(PartialEq, Eq, Clone, Debug)] #[derive(PartialEq, Eq, Clone, Debug)]

View File

@ -4,16 +4,15 @@
//! //!
//! This module defines network messages which describe peers and their //! This module defines network messages which describe peers and their
//! capabilities. //! capabilities.
use std::borrow::Cow;
use hashes::sha256d; use hashes::sha256d;
use io::{BufRead, Write}; use io::{BufRead, Write};
use crate::consensus::{self, encode, Decodable, Encodable, ReadExt}; use bitcoin::consensus::{encode, Decodable, Encodable, ReadExt};
use crate::internal_macros::impl_consensus_encoding; use crate::address::Address;
use crate::p2p; use crate::consensus::impl_consensus_encoding;
use crate::p2p::address::Address; use crate::ServiceFlags;
use crate::p2p::ServiceFlags;
use crate::prelude::{Cow, String};
// Some simple messages // Some simple messages
@ -61,7 +60,7 @@ impl VersionMessage {
start_height: i32, start_height: i32,
) -> VersionMessage { ) -> VersionMessage {
VersionMessage { VersionMessage {
version: p2p::PROTOCOL_VERSION, version: crate::PROTOCOL_VERSION,
services, services,
timestamp, timestamp,
receiver, receiver,
@ -126,7 +125,7 @@ impl Decodable for RejectReason {
0x41 => RejectReason::Dust, 0x41 => RejectReason::Dust,
0x42 => RejectReason::Fee, 0x42 => RejectReason::Fee,
0x43 => RejectReason::Checkpoint, 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 hex_lit::hex;
use super::*; use super::*;
use crate::consensus::encode::{deserialize, serialize}; use bitcoin::consensus::encode::{deserialize, serialize};
#[test] #[test]
fn version_message_test() { fn version_message_test() {