use std::{error::Error, fmt, sync::Arc};
use chrono::{DateTime, Utc};
use zebra_chain::{
block::{self, Block},
transaction::UnminedTx,
};
use crate::{meta_addr::MetaAddr, BoxError};
use super::{addr::AddrInVersion, inv::InventoryHash, types::*};
#[cfg(any(test, feature = "proptest-impl"))]
use proptest_derive::Arbitrary;
#[cfg(any(test, feature = "proptest-impl"))]
use zebra_chain::serialization::arbitrary::datetime_full;
#[derive(Clone, Eq, PartialEq, Debug)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
pub enum Message {
Version(VersionMessage),
Verack,
Ping(
Nonce,
),
Pong(
Nonce,
),
Reject {
message: String,
ccode: RejectReason,
reason: String,
data: Option<[u8; 32]>,
},
GetAddr,
Addr(Vec<MetaAddr>),
GetBlocks {
known_blocks: Vec<block::Hash>,
stop: Option<block::Hash>,
},
Inv(Vec<InventoryHash>),
GetHeaders {
known_blocks: Vec<block::Hash>,
stop: Option<block::Hash>,
},
Headers(Vec<block::CountedHeader>),
GetData(Vec<InventoryHash>),
Block(Arc<Block>),
Tx(UnminedTx),
NotFound(Vec<InventoryHash>),
Mempool,
FilterLoad {
filter: Filter,
hash_functions_count: u32,
tweak: Tweak,
flags: u8,
},
FilterAdd {
data: Vec<u8>,
},
FilterClear,
}
pub const MAX_USER_AGENT_LENGTH: usize = 256;
#[derive(Clone, Eq, PartialEq, Debug)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
pub struct VersionMessage {
pub version: Version,
pub services: PeerServices,
#[cfg_attr(
any(test, feature = "proptest-impl"),
proptest(strategy = "datetime_full()")
)]
pub timestamp: DateTime<Utc>,
pub address_recv: AddrInVersion,
pub address_from: AddrInVersion,
pub nonce: Nonce,
pub user_agent: String,
pub start_height: block::Height,
pub relay: bool,
}
pub const MAX_REJECT_MESSAGE_LENGTH: usize = 12;
pub const MAX_REJECT_REASON_LENGTH: usize = 111;
impl From<VersionMessage> for Message {
fn from(version_message: VersionMessage) -> Self {
Message::Version(version_message)
}
}
impl TryFrom<Message> for VersionMessage {
type Error = BoxError;
fn try_from(message: Message) -> Result<Self, Self::Error> {
match message {
Message::Version(version_message) => Ok(version_message),
_ => Err(format!(
"{} message is not a version message: {message:?}",
message.command()
)
.into()),
}
}
}
impl<E> From<E> for Message
where
E: Error,
{
fn from(e: E) -> Self {
let message = e
.to_string()
.escape_default()
.take(MAX_REJECT_MESSAGE_LENGTH)
.collect();
let reason = e
.source()
.map(ToString::to_string)
.unwrap_or_default()
.escape_default()
.take(MAX_REJECT_REASON_LENGTH)
.collect();
Message::Reject {
message,
ccode: RejectReason::Other,
reason,
data: None,
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
#[repr(u8)]
#[allow(missing_docs)]
pub enum RejectReason {
Malformed = 0x01,
Invalid = 0x10,
Obsolete = 0x11,
Duplicate = 0x12,
Nonstandard = 0x40,
Dust = 0x41,
InsufficientFee = 0x42,
Checkpoint = 0x43,
Other = 0x50,
}
impl fmt::Display for Message {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&match self {
Message::Version(VersionMessage {
version,
address_recv,
address_from,
user_agent,
..
}) => format!(
"version {{ network: {}, recv: {},_from: {}, user_agent: {:?} }}",
version,
address_recv.addr(),
address_from.addr(),
user_agent,
),
Message::Verack => "verack".to_string(),
Message::Ping(_) => "ping".to_string(),
Message::Pong(_) => "pong".to_string(),
Message::Reject {
message,
reason,
data,
..
} => format!(
"reject {{ message: {:?}, reason: {:?}, data: {} }}",
message,
reason,
if data.is_some() { "Some" } else { "None" },
),
Message::GetAddr => "getaddr".to_string(),
Message::Addr(addrs) => format!("addr {{ addrs: {} }}", addrs.len()),
Message::GetBlocks { known_blocks, stop } => format!(
"getblocks {{ known_blocks: {}, stop: {} }}",
known_blocks.len(),
if stop.is_some() { "Some" } else { "None" },
),
Message::Inv(invs) => format!("inv {{ invs: {} }}", invs.len()),
Message::GetHeaders { known_blocks, stop } => format!(
"getheaders {{ known_blocks: {}, stop: {} }}",
known_blocks.len(),
if stop.is_some() { "Some" } else { "None" },
),
Message::Headers(headers) => format!("headers {{ headers: {} }}", headers.len()),
Message::GetData(invs) => format!("getdata {{ invs: {} }}", invs.len()),
Message::Block(block) => format!(
"block {{ height: {}, hash: {} }}",
block
.coinbase_height()
.as_ref()
.map(|h| h.0.to_string())
.unwrap_or_else(|| "None".into()),
block.hash(),
),
Message::Tx(_) => "tx".to_string(),
Message::NotFound(invs) => format!("notfound {{ invs: {} }}", invs.len()),
Message::Mempool => "mempool".to_string(),
Message::FilterLoad { .. } => "filterload".to_string(),
Message::FilterAdd { .. } => "filteradd".to_string(),
Message::FilterClear => "filterclear".to_string(),
})
}
}
impl Message {
pub fn command(&self) -> &'static str {
match self {
Message::Version(_) => "version",
Message::Verack => "verack",
Message::Ping(_) => "ping",
Message::Pong(_) => "pong",
Message::Reject { .. } => "reject",
Message::GetAddr => "getaddr",
Message::Addr(_) => "addr",
Message::GetBlocks { .. } => "getblocks",
Message::Inv(_) => "inv",
Message::GetHeaders { .. } => "getheaders",
Message::Headers(_) => "headers",
Message::GetData(_) => "getdata",
Message::Block(_) => "block",
Message::Tx(_) => "tx",
Message::NotFound(_) => "notfound",
Message::Mempool => "mempool",
Message::FilterLoad { .. } => "filterload",
Message::FilterAdd { .. } => "filteradd",
Message::FilterClear => "filterclear",
}
}
}