From f1e6639374866aa13889f9d62dafef14c4d55fe0 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 14 Nov 2022 12:03:05 -0500 Subject: [PATCH] feat(net): authenticate sessions (#178) * Switch stream type of ActiveSession to EthStream * Start `StatusBuilder` for initializing the `Status` message required for the handshake * Add `Hardfork` for `Status` default forkid * Add `MAINNET_GENESIS` constant * finish `StatusBuilder` * initialize eth streams in session * add status, hello, and fork filter to session manager * fix status builder example * add status and hello to network config * will probably remove * removing status and hello from networkconfig * move forkid to primitives * change imports for forkid * add hardfork to primitives * remove hardfork and forkid from eth-wire * fix remaining eth-wire forkid references * put mainnet genesis in constants, remove NodeId * replace NodeId with PeerId * the only NodeId remaining is inherited from enr * PeerId still needs to be documented * also run cargo fmt * replace loop with iter().any() * ignore missing docs for hardforks * use correct PeerId for Discv4::bind example test * document PeerId as secp256k1 public key * cargo fmt * temporarily allow too_many_arguments * the authenticate and start_pending_incoming_session methods have many arguments, we can reconsider the lint or fix the methods at a later point --- crates/net/discv4/src/config.rs | 4 +- crates/net/discv4/src/lib.rs | 72 +++--- crates/net/discv4/src/mock.rs | 26 +- crates/net/discv4/src/node.rs | 26 +- crates/net/discv4/src/proto.rs | 10 +- crates/net/eth-wire/Cargo.toml | 3 + crates/net/eth-wire/src/builder.rs | 145 +++++++++++ crates/net/eth-wire/src/capability.rs | 10 + crates/net/eth-wire/src/error.rs | 4 +- crates/net/eth-wire/src/ethstream.rs | 8 +- crates/net/eth-wire/src/lib.rs | 7 +- crates/net/eth-wire/src/p2pstream.rs | 1 + crates/net/eth-wire/src/types/mod.rs | 2 - crates/net/eth-wire/src/types/status.rs | 32 ++- crates/net/network/src/config.rs | 7 +- crates/net/network/src/discovery.rs | 13 +- crates/net/network/src/fetch.rs | 24 +- crates/net/network/src/lib.rs | 5 +- crates/net/network/src/manager.rs | 12 +- crates/net/network/src/message.rs | 5 +- crates/net/network/src/network.rs | 8 +- crates/net/network/src/peers.rs | 26 +- crates/net/network/src/session/active.rs | 11 +- crates/net/network/src/session/handle.rs | 26 +- crates/net/network/src/session/mod.rs | 143 ++++++++--- crates/net/network/src/state.rs | 21 +- crates/net/network/src/swarm.rs | 26 +- crates/primitives/src/constants.rs | 5 + .../src/types => primitives/src}/forkid.rs | 2 +- crates/primitives/src/hardfork.rs | 225 ++++++++++++++++++ crates/primitives/src/lib.rs | 12 + 31 files changed, 714 insertions(+), 207 deletions(-) create mode 100644 crates/net/eth-wire/src/builder.rs create mode 100644 crates/primitives/src/constants.rs rename crates/{net/eth-wire/src/types => primitives/src}/forkid.rs (99%) create mode 100644 crates/primitives/src/hardfork.rs diff --git a/crates/net/discv4/src/config.rs b/crates/net/discv4/src/config.rs index bcafafc15..cccd8e0cd 100644 --- a/crates/net/discv4/src/config.rs +++ b/crates/net/discv4/src/config.rs @@ -24,7 +24,7 @@ pub struct Discv4Config { pub find_node_timeout: Duration, /// The duration we set for neighbours responses pub neighbours_timeout: Duration, - /// A set of lists that permit or ban IP's or NodeIds from the server. See + /// A set of lists that permit or ban IP's or PeerIds from the server. See /// `crate::PermitBanList`. pub permit_ban_list: PermitBanList, /// Set the default duration for which nodes are banned for. This timeouts are checked every 5 @@ -110,7 +110,7 @@ impl Discv4ConfigBuilder { self } - /// A set of lists that permit or ban IP's or NodeIds from the server. See + /// A set of lists that permit or ban IP's or PeerIds from the server. See /// `crate::PermitBanList`. pub fn permit_ban_list(&mut self, list: PermitBanList) -> &mut Self { self.config.permit_ban_list = list; diff --git a/crates/net/discv4/src/lib.rs b/crates/net/discv4/src/lib.rs index 2d9f89969..f006905c4 100644 --- a/crates/net/discv4/src/lib.rs +++ b/crates/net/discv4/src/lib.rs @@ -30,7 +30,7 @@ use discv5::{ }, ConnectionDirection, ConnectionState, }; -use reth_primitives::{H256, H512}; +use reth_primitives::{PeerId, H256}; use secp256k1::SecretKey; use std::{ cell::RefCell, @@ -67,9 +67,6 @@ pub mod mock; /// reexport to get public ip. pub use public_ip; -/// Identifier for nodes. -pub type NodeId = H512; - /// The default port for discv4 via UDP /// /// Note: the default TCP port is the same. @@ -140,12 +137,13 @@ impl Discv4 { /// use std::str::FromStr; /// use rand::thread_rng; /// use secp256k1::SECP256K1; - /// use reth_discv4::{Discv4, Discv4Config, NodeId, NodeRecord}; + /// use reth_primitives::PeerId; + /// use reth_discv4::{Discv4, Discv4Config, NodeRecord}; /// # async fn t() -> io::Result<()> { /// // generate a (random) keypair /// let mut rng = thread_rng(); /// let (secret_key, pk) = SECP256K1.generate_keypair(&mut rng); - /// let id = NodeId::from_slice(&pk.serialize_uncompressed()[1..]); + /// let id = PeerId::from_slice(&pk.serialize_uncompressed()[1..]); /// /// let socket = SocketAddr::from_str("0.0.0.0:0").unwrap(); /// let local_enr = NodeRecord { @@ -212,11 +210,11 @@ impl Discv4 { } /// Looks up the given node id - pub async fn lookup(&self, node_id: NodeId) -> Result, Discv4Error> { + pub async fn lookup(&self, node_id: PeerId) -> Result, Discv4Error> { self.lookup_node(Some(node_id)).await } - async fn lookup_node(&self, node_id: Option) -> Result, Discv4Error> { + async fn lookup_node(&self, node_id: Option) -> Result, Discv4Error> { let (tx, rx) = oneshot::channel(); let cmd = Discv4Command::Lookup { node_id, tx: Some(tx) }; self.to_service.send(cmd).await?; @@ -269,9 +267,9 @@ pub struct Discv4Service { /// followup `FindNode` requests.... Buffering them effectively prevents high `Ping` peaks. queued_pings: VecDeque<(NodeRecord, PingReason)>, /// Currently active pings to specific nodes. - pending_pings: HashMap, + pending_pings: HashMap, /// Currently active FindNode requests - pending_find_nodes: HashMap, + pending_find_nodes: HashMap, /// Commands listener commands_rx: Option>, /// All subscribers for table updates @@ -377,8 +375,8 @@ impl Discv4Service { &mut self.local_enr } - /// Returns true if the given NodeId is currently in the bucket - pub fn contains_node(&self, id: NodeId) -> bool { + /// Returns true if the given PeerId is currently in the bucket + pub fn contains_node(&self, id: PeerId) -> bool { let key = kad_key(id); self.kbuckets.get_index(&key).is_some() } @@ -431,7 +429,7 @@ impl Discv4Service { // // To guard against traffic amplification attacks, Neighbors replies should only be sent if the // sender of FindNode has been verified by the endpoint proof procedure. - pub fn lookup(&mut self, target: NodeId) { + pub fn lookup(&mut self, target: PeerId) { self.lookup_with(target, None) } @@ -445,7 +443,7 @@ impl Discv4Service { /// This takes an optional Sender through which all successfully discovered nodes are sent once /// the request has finished. #[instrument(skip_all, fields(?target), target = "net::discv4")] - fn lookup_with(&mut self, target: NodeId, tx: Option) { + fn lookup_with(&mut self, target: PeerId, tx: Option) { trace!("Starting lookup"); let key = kad_key(target); @@ -499,7 +497,7 @@ impl Discv4Service { /// /// This allows applications, for whatever reason, to remove nodes from the local routing /// table. Returns `true` if the node was in the table and `false` otherwise. - pub fn remove_node(&mut self, node_id: NodeId) -> bool { + pub fn remove_node(&mut self, node_id: PeerId) -> bool { let key = kad_key(node_id); let removed = self.kbuckets.remove(&key); if removed { @@ -559,7 +557,7 @@ impl Discv4Service { } /// Message handler for an incoming `Ping` - fn on_ping(&mut self, ping: Ping, remote_addr: SocketAddr, remote_id: NodeId, hash: H256) { + fn on_ping(&mut self, ping: Ping, remote_addr: SocketAddr, remote_id: PeerId, hash: H256) { // update the record let record = NodeRecord { address: ping.from.address, @@ -611,7 +609,7 @@ impl Discv4Service { } /// Message handler for an incoming `Pong`. - fn on_pong(&mut self, pong: Pong, remote_addr: SocketAddr, remote_id: NodeId) { + fn on_pong(&mut self, pong: Pong, remote_addr: SocketAddr, remote_id: PeerId) { if self.is_expired(pong.expire) { return } @@ -654,7 +652,7 @@ impl Discv4Service { } /// Handler for incoming `FindNode` message - fn on_find_node(&mut self, msg: FindNode, remote_addr: SocketAddr, node_id: NodeId) { + fn on_find_node(&mut self, msg: FindNode, remote_addr: SocketAddr, node_id: PeerId) { match self.node_status(node_id, remote_addr) { NodeEntryStatus::IsLocal => { // received address from self @@ -675,7 +673,7 @@ impl Discv4Service { /// Handler for incoming `Neighbours` messages that are handled if they're responses to /// `FindNode` requests - fn on_neighbours(&mut self, msg: Neighbours, remote_addr: SocketAddr, node_id: NodeId) { + fn on_neighbours(&mut self, msg: Neighbours, remote_addr: SocketAddr, node_id: PeerId) { // check if this request was expected let ctx = match self.pending_find_nodes.entry(node_id) { Entry::Occupied(mut entry) => { @@ -732,7 +730,7 @@ impl Discv4Service { } /// Sends a Neighbours packet for `target` to the given addr - fn respond_closest(&mut self, target: NodeId, to: SocketAddr) { + fn respond_closest(&mut self, target: PeerId, to: SocketAddr) { let key = kad_key(target); let expire = self.send_neighbours_timeout(); let all_nodes = self.kbuckets.closest_values(&key).collect::>(); @@ -746,7 +744,7 @@ impl Discv4Service { } /// Returns the current status of the node - fn node_status(&mut self, node: NodeId, addr: SocketAddr) -> NodeEntryStatus { + fn node_status(&mut self, node: PeerId, addr: SocketAddr) -> NodeEntryStatus { if node == self.local_enr.id { debug!(?node, target = "net::disc", "Got an incoming discovery request from self"); return NodeEntryStatus::IsLocal @@ -807,7 +805,7 @@ impl Discv4Service { } /// Removes the node from the table - fn expire_node_request(&mut self, node_id: NodeId) { + fn expire_node_request(&mut self, node_id: PeerId) { let key = kad_key(node_id); self.kbuckets.remove(&key); } @@ -976,7 +974,7 @@ pub(crate) async fn send_loop(udp: Arc, rx: EgressReceiver) { } /// Continuously awaits new incoming messages and sends them back through the channel. -pub(crate) async fn receive_loop(udp: Arc, tx: IngressSender, local_id: NodeId) { +pub(crate) async fn receive_loop(udp: Arc, tx: IngressSender, local_id: PeerId) { loop { let mut buf = [0; MAX_PACKET_SIZE]; let res = udp.recv_from(&mut buf).await; @@ -1010,7 +1008,7 @@ pub(crate) async fn receive_loop(udp: Arc, tx: IngressSender, local_i /// The commands sent from the frontend to the service enum Discv4Command { - Lookup { node_id: Option, tx: Option }, + Lookup { node_id: Option, tx: Option }, Updates(OneshotSender>), } @@ -1036,7 +1034,7 @@ struct PingRequest { reason: PingReason, } -/// Rotates the NodeId that is periodically looked up. +/// Rotates the PeerId that is periodically looked up. /// /// By selecting different targets, the lookups will be seeded with different ALPHA seed nodes. #[derive(Debug)] @@ -1066,13 +1064,13 @@ impl Default for LookupTargetRotator { impl LookupTargetRotator { /// this will return the next node id to lookup - fn next(&mut self, local: &NodeId) -> NodeId { + fn next(&mut self, local: &PeerId) -> PeerId { self.counter += 1; self.counter %= self.interval; if self.counter == 0 { return *local } - NodeId::random() + PeerId::random() } } @@ -1087,7 +1085,7 @@ struct LookupContext { impl LookupContext { /// Create new context for a recursive lookup fn new( - target: NodeId, + target: PeerId, nearest_nodes: impl IntoIterator, listener: Option, ) -> Self { @@ -1107,7 +1105,7 @@ impl LookupContext { } /// Returns the target of this lookup - fn target(&self) -> NodeId { + fn target(&self) -> PeerId { self.inner.target } @@ -1132,7 +1130,7 @@ impl LookupContext { } /// Marks the node as queried - fn mark_queried(&self, id: NodeId) { + fn mark_queried(&self, id: PeerId) { if let Some((_, node)) = self.inner.closest_nodes.borrow_mut().iter_mut().find(|(_, node)| node.record.id == id) { @@ -1141,7 +1139,7 @@ impl LookupContext { } /// Marks the node as responded - fn mark_responded(&self, id: NodeId) { + fn mark_responded(&self, id: PeerId) { if let Some((_, node)) = self.inner.closest_nodes.borrow_mut().iter_mut().find(|(_, node)| node.record.id == id) { @@ -1159,7 +1157,7 @@ impl LookupContext { unsafe impl Send for LookupContext {} struct LookupContextInner { - target: NodeId, + target: PeerId, /// The closest nodes closest_nodes: RefCell>, /// A listener for all the nodes retrieved in this lookup @@ -1249,7 +1247,7 @@ enum PingReason { /// /// Once the expected PONG is received, the endpoint proof is complete and the find node can be /// answered. - FindNode(NodeId, NodeEntryStatus), + FindNode(PeerId, NodeEntryStatus), /// Part of a lookup to ensure endpoint is proven. Lookup(NodeRecord, LookupContext), } @@ -1260,7 +1258,7 @@ pub enum TableUpdate { /// A new node was inserted to the table. Added(NodeRecord), /// Node that was removed from the table - Removed(NodeId), + Removed(PeerId), /// A series of updates Batch(Vec), } @@ -1276,7 +1274,7 @@ mod tests { #[test] fn test_local_rotator() { - let id = NodeId::random(); + let id = PeerId::random(); let mut rotator = LookupTargetRotator::local_only(); assert_eq!(rotator.next(&id), id); assert_eq!(rotator.next(&id), id); @@ -1284,7 +1282,7 @@ mod tests { #[test] fn test_rotator() { - let id = NodeId::random(); + let id = PeerId::random(); let mut rotator = LookupTargetRotator::default(); assert_eq!(rotator.next(&id), id); assert_ne!(rotator.next(&id), id); @@ -1301,7 +1299,7 @@ mod tests { let local_addr = service.local_addr(); for idx in 0..MAX_NODES_PING { - let node = NodeRecord::new(local_addr, NodeId::random()); + let node = NodeRecord::new(local_addr, PeerId::random()); service.add_node(node); assert!(service.pending_pings.contains_key(&node.id)); assert_eq!(service.pending_pings.len(), idx + 1); diff --git a/crates/net/discv4/src/mock.rs b/crates/net/discv4/src/mock.rs index c4565911d..87d04cdd9 100644 --- a/crates/net/discv4/src/mock.rs +++ b/crates/net/discv4/src/mock.rs @@ -6,7 +6,7 @@ use crate::{ node::NodeRecord, proto::{FindNode, Message, Neighbours, NodeEndpoint, Packet, Ping, Pong}, receive_loop, send_loop, Discv4, Discv4Config, Discv4Service, EgressSender, IngressEvent, - IngressReceiver, NodeId, SAFE_MAX_DATAGRAM_NEIGHBOUR_RECORDS, + IngressReceiver, PeerId, SAFE_MAX_DATAGRAM_NEIGHBOUR_RECORDS, }; use rand::{thread_rng, Rng, RngCore}; use reth_primitives::H256; @@ -40,8 +40,8 @@ pub struct MockDiscovery { ingress: IngressReceiver, /// Sender for sending outgoing messages egress: EgressSender, - pending_pongs: HashSet, - pending_neighbours: HashMap>, + pending_pongs: HashSet, + pending_neighbours: HashMap>, command_rx: mpsc::Receiver, } @@ -51,7 +51,7 @@ impl MockDiscovery { let mut rng = thread_rng(); let socket = SocketAddr::from_str("0.0.0.0:0").unwrap(); let (secret_key, pk) = SECP256K1.generate_keypair(&mut rng); - let id = NodeId::from_slice(&pk.serialize_uncompressed()[1..]); + let id = PeerId::from_slice(&pk.serialize_uncompressed()[1..]); let socket = Arc::new(UdpSocket::bind(socket).await?); let local_addr = socket.local_addr()?; let local_enr = NodeRecord { @@ -95,12 +95,12 @@ impl MockDiscovery { } /// Queue a pending pong. - pub fn queue_pong(&mut self, from: NodeId) { + pub fn queue_pong(&mut self, from: PeerId) { self.pending_pongs.insert(from); } /// Queue a pending Neighbours response. - pub fn queue_neighbours(&mut self, target: NodeId, nodes: Vec) { + pub fn queue_neighbours(&mut self, target: PeerId, nodes: Vec) { self.pending_neighbours.insert(target, nodes); } @@ -195,8 +195,8 @@ pub enum MockEvent { /// Command for interacting with the `MockDiscovery` service pub enum MockCommand { - MockPong { node_id: NodeId }, - MockNeighbours { target: NodeId, nodes: Vec }, + MockPong { node_id: PeerId }, + MockNeighbours { target: PeerId, nodes: Vec }, } /// Creates a new testing instance for [`Discv4`] and its service @@ -209,7 +209,7 @@ pub async fn create_discv4_with_config(config: Discv4Config) -> (Discv4, Discv4S let mut rng = thread_rng(); let socket = SocketAddr::from_str("0.0.0.0:0").unwrap(); let (secret_key, pk) = SECP256K1.generate_keypair(&mut rng); - let id = NodeId::from_slice(&pk.serialize_uncompressed()[1..]); + let id = PeerId::from_slice(&pk.serialize_uncompressed()[1..]); let external_addr = public_ip::addr().await.unwrap_or_else(|| socket.ip()); let local_enr = NodeRecord { address: external_addr, tcp_port: socket.port(), udp_port: socket.port(), id }; @@ -231,21 +231,21 @@ pub fn rng_endpoint(rng: &mut impl Rng) -> NodeEndpoint { pub fn rng_record(rng: &mut impl RngCore) -> NodeRecord { let NodeEndpoint { address, udp_port, tcp_port } = rng_endpoint(rng); - NodeRecord { address, tcp_port, udp_port, id: NodeId::random() } + NodeRecord { address, tcp_port, udp_port, id: PeerId::random() } } pub fn rng_ipv6_record(rng: &mut impl RngCore) -> NodeRecord { let mut ip = [0u8; 16]; rng.fill_bytes(&mut ip); let address = IpAddr::V6(ip.into()); - NodeRecord { address, tcp_port: rng.gen(), udp_port: rng.gen(), id: NodeId::random() } + NodeRecord { address, tcp_port: rng.gen(), udp_port: rng.gen(), id: PeerId::random() } } pub fn rng_ipv4_record(rng: &mut impl RngCore) -> NodeRecord { let mut ip = [0u8; 4]; rng.fill_bytes(&mut ip); let address = IpAddr::V4(ip.into()); - NodeRecord { address, tcp_port: rng.gen(), udp_port: rng.gen(), id: NodeId::random() } + NodeRecord { address, tcp_port: rng.gen(), udp_port: rng.gen(), id: PeerId::random() } } pub fn rng_message(rng: &mut impl RngCore) -> Message { @@ -256,7 +256,7 @@ pub fn rng_message(rng: &mut impl RngCore) -> Message { expire: rng.gen(), }), 2 => Message::Pong(Pong { to: rng_endpoint(rng), echo: H256::random(), expire: rng.gen() }), - 3 => Message::FindNode(FindNode { id: NodeId::random(), expire: rng.gen() }), + 3 => Message::FindNode(FindNode { id: PeerId::random(), expire: rng.gen() }), 4 => { let num: usize = rng.gen_range(1..=SAFE_MAX_DATAGRAM_NEIGHBOUR_RECORDS); Message::Neighbours(Neighbours { diff --git a/crates/net/discv4/src/node.rs b/crates/net/discv4/src/node.rs index fa9dbab3d..035ff7902 100644 --- a/crates/net/discv4/src/node.rs +++ b/crates/net/discv4/src/node.rs @@ -1,4 +1,4 @@ -use crate::{proto::Octets, NodeId}; +use crate::{proto::Octets, PeerId}; use bytes::{Buf, BufMut}; use generic_array::GenericArray; use reth_primitives::keccak256; @@ -13,10 +13,10 @@ use url::{Host, Url}; /// The key type for the table. #[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub(crate) struct NodeKey(pub(crate) NodeId); +pub(crate) struct NodeKey(pub(crate) PeerId); -impl From for NodeKey { - fn from(value: NodeId) -> Self { +impl From for NodeKey { + fn from(value: PeerId) -> Self { NodeKey(value) } } @@ -29,9 +29,9 @@ impl From for discv5::Key { } } -/// Converts a `NodeId` into the required `Key` type for the table +/// Converts a `PeerId` into the required `Key` type for the table #[inline] -pub(crate) fn kad_key(node: NodeId) -> discv5::Key { +pub(crate) fn kad_key(node: PeerId) -> discv5::Key { discv5::kbucket::Key::from(NodeKey::from(node)) } @@ -45,20 +45,20 @@ pub struct NodeRecord { /// UDP discovery port. pub udp_port: u16, /// Public key of the discovery service - pub id: NodeId, + pub id: PeerId, } impl NodeRecord { /// Derive the [`NodeRecord`] from the secret key and addr pub fn from_secret_key(addr: SocketAddr, sk: &SecretKey) -> Self { let pk = secp256k1::PublicKey::from_secret_key(SECP256K1, sk); - let id = NodeId::from_slice(&pk.serialize_uncompressed()[1..]); + let id = PeerId::from_slice(&pk.serialize_uncompressed()[1..]); Self::new(addr, id) } /// Creates a new record #[allow(unused)] - pub(crate) fn new(addr: SocketAddr, id: NodeId) -> Self { + pub(crate) fn new(addr: SocketAddr, id: PeerId) -> Self { Self { address: addr.ip(), tcp_port: addr.port(), udp_port: addr.port(), id } } @@ -112,7 +112,7 @@ impl FromStr for NodeRecord { let id = url .username() - .parse::() + .parse::() .map_err(|e| NodeRecordParseError::InvalidId(e.to_string()))?; Ok(Self { address, id, tcp_port: port, udp_port: port }) @@ -126,7 +126,7 @@ impl Encodable for NodeRecord { octets: Octets, udp_port: u16, tcp_port: u16, - id: NodeId, + id: PeerId, } let octets = match self.address { @@ -185,7 +185,7 @@ mod tests { address: IpAddr::V4(ip.into()), tcp_port: rng.gen(), udp_port: rng.gen(), - id: NodeId::random(), + id: PeerId::random(), }; let mut buf = BytesMut::new(); @@ -206,7 +206,7 @@ mod tests { address: IpAddr::V6(ip.into()), tcp_port: rng.gen(), udp_port: rng.gen(), - id: NodeId::random(), + id: PeerId::random(), }; let mut buf = BytesMut::new(); diff --git a/crates/net/discv4/src/proto.rs b/crates/net/discv4/src/proto.rs index 472346f52..8a6945aaf 100644 --- a/crates/net/discv4/src/proto.rs +++ b/crates/net/discv4/src/proto.rs @@ -1,6 +1,6 @@ #![allow(missing_docs)] -use crate::{error::DecodePacketError, node::NodeRecord, NodeId, MAX_PACKET_SIZE, MIN_PACKET_SIZE}; +use crate::{error::DecodePacketError, node::NodeRecord, PeerId, MAX_PACKET_SIZE, MIN_PACKET_SIZE}; use bytes::{Buf, BufMut, Bytes, BytesMut}; use reth_primitives::{keccak256, H256}; use reth_rlp::{Decodable, DecodeError, Encodable, Header}; @@ -136,7 +136,7 @@ impl Message { let msg = secp256k1::Message::from_slice(keccak256(&packet[97..]).as_bytes())?; let pk = SECP256K1.recover_ecdsa(&msg, &recoverable_sig)?; - let node_id = NodeId::from_slice(&pk.serialize_uncompressed()[1..]); + let node_id = PeerId::from_slice(&pk.serialize_uncompressed()[1..]); let msg_type = packet[97]; let payload = &mut &packet[98..]; @@ -156,7 +156,7 @@ impl Message { #[derive(Debug)] pub struct Packet { pub msg: Message, - pub node_id: NodeId, + pub node_id: PeerId, pub hash: H256, } @@ -223,7 +223,7 @@ impl From for NodeEndpoint { /// A [FindNode packet](https://github.com/ethereum/devp2p/blob/master/discv4.md#findnode-packet-0x03).). #[derive(Clone, Copy, Debug, Eq, PartialEq, RlpEncodable, RlpDecodable)] pub struct FindNode { - pub id: NodeId, + pub id: PeerId, pub expire: u64, } @@ -499,7 +499,7 @@ mod tests { for _ in 0..100 { let msg = rng_message(&mut rng); let (secret_key, pk) = SECP256K1.generate_keypair(&mut rng); - let sender_id = NodeId::from_slice(&pk.serialize_uncompressed()[1..]); + let sender_id = PeerId::from_slice(&pk.serialize_uncompressed()[1..]); let (buf, _) = msg.encode(&secret_key); diff --git a/crates/net/eth-wire/Cargo.toml b/crates/net/eth-wire/Cargo.toml index e754bec02..ff27c7e9e 100644 --- a/crates/net/eth-wire/Cargo.toml +++ b/crates/net/eth-wire/Cargo.toml @@ -17,6 +17,9 @@ reth-ecies = { path = "../ecies" } reth-primitives = { path = "../../primitives" } reth-rlp = { path = "../../common/rlp", features = ["alloc", "derive", "std", "ethereum-types", "smol_str"] } +# used for Chain and builders +ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = false } + #used for forkid crc = "1" maplit = "1" diff --git a/crates/net/eth-wire/src/builder.rs b/crates/net/eth-wire/src/builder.rs new file mode 100644 index 000000000..f1c0dd6af --- /dev/null +++ b/crates/net/eth-wire/src/builder.rs @@ -0,0 +1,145 @@ +//! Builder structs for [`Status`](crate::types::Status) and [`Hello`](crate::types::Hello) +//! messages. + +use crate::{ + capability::Capability, + p2pstream::{HelloMessage, ProtocolVersion}, + EthVersion, Status, +}; +use reth_primitives::{Chain, ForkId, PeerId, H256, U256}; + +/// Builder for [`Status`](crate::types::Status) messages. +/// +/// # Example +/// ``` +/// use reth_eth_wire::EthVersion; +/// use reth_primitives::{Chain, U256, H256, MAINNET_GENESIS, Hardfork}; +/// use reth_eth_wire::types::Status; +/// +/// // this is just an example status message! +/// let status = Status::builder() +/// .version(EthVersion::Eth66.into()) +/// .chain(Chain::Named(ethers_core::types::Chain::Mainnet)) +/// .total_difficulty(U256::from(100)) +/// .blockhash(H256::from(MAINNET_GENESIS)) +/// .genesis(H256::from(MAINNET_GENESIS)) +/// .forkid(Hardfork::Latest.fork_id()) +/// .build(); +/// +/// assert_eq!( +/// status, +/// Status { +/// version: EthVersion::Eth66.into(), +/// chain: Chain::Named(ethers_core::types::Chain::Mainnet), +/// total_difficulty: U256::from(100), +/// blockhash: H256::from(MAINNET_GENESIS), +/// genesis: H256::from(MAINNET_GENESIS), +/// forkid: Hardfork::Latest.fork_id(), +/// } +/// ); +/// ``` +#[derive(Debug, Default)] +pub struct StatusBuilder { + status: Status, +} + +impl StatusBuilder { + /// Consumes the type and creates the actual [`Status`](crate::types::Status) message. + pub fn build(self) -> Status { + self.status + } + + /// Sets the protocol version. + pub fn version(mut self, version: u8) -> Self { + self.status.version = version; + self + } + + /// Sets the chain id. + pub fn chain(mut self, chain: Chain) -> Self { + self.status.chain = chain; + self + } + + /// Sets the total difficulty. + pub fn total_difficulty(mut self, total_difficulty: U256) -> Self { + self.status.total_difficulty = total_difficulty; + self + } + + /// Sets the block hash. + pub fn blockhash(mut self, blockhash: H256) -> Self { + self.status.blockhash = blockhash; + self + } + + /// Sets the genesis hash. + pub fn genesis(mut self, genesis: H256) -> Self { + self.status.genesis = genesis; + self + } + + /// Sets the fork id. + pub fn forkid(mut self, forkid: ForkId) -> Self { + self.status.forkid = forkid; + self + } +} + +/// Builder for [`Hello`](crate::types::Hello) messages. +pub struct HelloBuilder { + hello: HelloMessage, +} + +impl HelloBuilder { + /// Creates a new [`HelloBuilder`](crate::builder::HelloBuilder) with default [`Hello`] values, + /// and a `PeerId` corresponding to the given pubkey. + pub fn new(pubkey: PeerId) -> Self { + Self { + hello: HelloMessage { + protocol_version: ProtocolVersion::V5, + // TODO: proper client versioning + client_version: "Ethereum/1.0.0".to_string(), + capabilities: vec![EthVersion::Eth67.into()], + // TODO: default port config + port: 30303, + id: pubkey, + }, + } + } + + /// Consumes the type and creates the actual [`Hello`](crate::types::Hello) message. + pub fn build(self) -> HelloMessage { + self.hello + } + + /// Sets the protocol version. + pub fn protocol_version(mut self, protocol_version: ProtocolVersion) -> Self { + self.hello.protocol_version = protocol_version; + self + } + + /// Sets the client version. + pub fn client_version(mut self, client_version: String) -> Self { + self.hello.client_version = client_version; + self + } + + /// Sets the capabilities. + pub fn capabilities(mut self, capabilities: Vec) -> Self { + self.hello.capabilities = capabilities; + self + } + + /// Sets the port. + pub fn port(mut self, port: u16) -> Self { + self.hello.port = port; + self + } + + /// Sets the node id. + pub fn id(mut self, id: PeerId) -> Self { + self.hello.id = id; + self + } +} diff --git a/crates/net/eth-wire/src/capability.rs b/crates/net/eth-wire/src/capability.rs index 3ee3c9803..192afd3a2 100644 --- a/crates/net/eth-wire/src/capability.rs +++ b/crates/net/eth-wire/src/capability.rs @@ -91,6 +91,16 @@ impl Capabilities { } } +impl From> for Capabilities { + fn from(value: Vec) -> Self { + Self { + eth_66: value.iter().any(Capability::is_eth_v66), + eth_67: value.iter().any(Capability::is_eth_v67), + inner: value, + } + } +} + impl Encodable for Capabilities { fn encode(&self, out: &mut dyn BufMut) { self.inner.encode(out) diff --git a/crates/net/eth-wire/src/error.rs b/crates/net/eth-wire/src/error.rs index dd7df28a3..929fbae22 100644 --- a/crates/net/eth-wire/src/error.rs +++ b/crates/net/eth-wire/src/error.rs @@ -1,9 +1,9 @@ //! Error cases when handling a [`crate::EthStream`] use std::io; -use reth_primitives::{Chain, H256}; +use reth_primitives::{Chain, ValidationError, H256}; -use crate::{capability::SharedCapabilityError, types::forkid::ValidationError}; +use crate::capability::SharedCapabilityError; /// Errors when sending/receiving messages #[derive(thiserror::Error, Debug)] diff --git a/crates/net/eth-wire/src/ethstream.rs b/crates/net/eth-wire/src/ethstream.rs index e7fbd205e..775ef261e 100644 --- a/crates/net/eth-wire/src/ethstream.rs +++ b/crates/net/eth-wire/src/ethstream.rs @@ -1,10 +1,11 @@ use crate::{ error::{EthStreamError, HandshakeError}, - types::{forkid::ForkFilter, EthMessage, ProtocolMessage, Status}, + types::{EthMessage, ProtocolMessage, Status}, }; use bytes::{Bytes, BytesMut}; use futures::{ready, Sink, SinkExt, StreamExt}; use pin_project::pin_project; +use reth_primitives::ForkFilter; use reth_rlp::{Decodable, Encodable}; use std::{ pin::Pin, @@ -117,6 +118,7 @@ where /// An `EthStream` wraps over any `Stream` that yields bytes and makes it /// compatible with eth-networking protocol messages, which get RLP encoded/decoded. #[pin_project] +#[derive(Debug)] pub struct EthStream { #[pin] inner: S, @@ -203,7 +205,7 @@ where mod tests { use crate::{ p2pstream::{HelloMessage, ProtocolVersion, UnauthedP2PStream}, - types::{broadcast::BlockHashNumber, forkid::ForkFilter, EthMessage, Status}, + types::{broadcast::BlockHashNumber, EthMessage, Status}, EthStream, PassthroughCodec, }; use futures::{SinkExt, StreamExt}; @@ -214,7 +216,7 @@ mod tests { use crate::{capability::Capability, types::EthVersion}; use ethers_core::types::Chain; - use reth_primitives::{H256, U256}; + use reth_primitives::{ForkFilter, H256, U256}; use super::UnauthedEthStream; diff --git a/crates/net/eth-wire/src/lib.rs b/crates/net/eth-wire/src/lib.rs index 2404aa9ea..b770044fa 100644 --- a/crates/net/eth-wire/src/lib.rs +++ b/crates/net/eth-wire/src/lib.rs @@ -11,12 +11,17 @@ pub use tokio_util::codec::{ LengthDelimitedCodec as PassthroughCodec, LengthDelimitedCodecError as PassthroughCodecError, }; +pub mod builder; pub mod capability; pub mod error; mod ethstream; mod p2pstream; mod pinger; +pub use builder::*; pub mod types; pub use types::*; -pub use ethstream::{EthStream, UnauthedEthStream}; +pub use crate::{ + ethstream::{EthStream, UnauthedEthStream}, + p2pstream::{HelloMessage, P2PStream, UnauthedP2PStream}, +}; diff --git a/crates/net/eth-wire/src/p2pstream.rs b/crates/net/eth-wire/src/p2pstream.rs index dc71dd699..68b0c2475 100644 --- a/crates/net/eth-wire/src/p2pstream.rs +++ b/crates/net/eth-wire/src/p2pstream.rs @@ -138,6 +138,7 @@ where /// A P2PStream wraps over any `Stream` that yields bytes and makes it compatible with `p2p` /// protocol messages. #[pin_project] +#[derive(Debug)] pub struct P2PStream { #[pin] inner: S, diff --git a/crates/net/eth-wire/src/types/mod.rs b/crates/net/eth-wire/src/types/mod.rs index 6e8403002..f330958ee 100644 --- a/crates/net/eth-wire/src/types/mod.rs +++ b/crates/net/eth-wire/src/types/mod.rs @@ -6,8 +6,6 @@ pub use status::Status; pub mod version; pub use version::EthVersion; -pub mod forkid; - pub mod message; pub use message::{EthMessage, EthMessageID, ProtocolMessage}; diff --git a/crates/net/eth-wire/src/types/status.rs b/crates/net/eth-wire/src/types/status.rs index 6b9681618..33b3634d5 100644 --- a/crates/net/eth-wire/src/types/status.rs +++ b/crates/net/eth-wire/src/types/status.rs @@ -1,5 +1,6 @@ -use super::forkid::ForkId; -use reth_primitives::{Chain, H256, U256}; +use crate::{EthVersion, StatusBuilder}; + +use reth_primitives::{Chain, ForkId, Hardfork, H256, MAINNET_GENESIS, U256}; use reth_rlp::{RlpDecodable, RlpEncodable}; use std::fmt::{Debug, Display}; @@ -37,6 +38,13 @@ pub struct Status { pub forkid: ForkId, } +impl Status { + /// Helper for returning a builder for the status message. + pub fn builder() -> StatusBuilder { + Default::default() + } +} + impl Display for Status { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let hexed_blockhash = hex::encode(self.blockhash); @@ -84,18 +92,28 @@ impl Debug for Status { } } +impl Default for Status { + fn default() -> Self { + Status { + version: EthVersion::Eth67 as u8, + chain: Chain::Named(ethers_core::types::Chain::Mainnet), + total_difficulty: U256::zero(), + blockhash: MAINNET_GENESIS, + genesis: MAINNET_GENESIS, + forkid: Hardfork::Homestead.fork_id(), + } + } +} + #[cfg(test)] mod tests { use ethers_core::types::Chain as NamedChain; use hex_literal::hex; - use reth_primitives::{Chain, H256, U256}; + use reth_primitives::{Chain, ForkHash, ForkId, H256, U256}; use reth_rlp::{Decodable, Encodable}; use std::str::FromStr; - use crate::types::{ - forkid::{ForkHash, ForkId}, - EthVersion, Status, - }; + use crate::types::{EthVersion, Status}; #[test] fn encode_eth_status_message() { diff --git a/crates/net/network/src/config.rs b/crates/net/network/src/config.rs index a73f08b7d..4eb0641ce 100644 --- a/crates/net/network/src/config.rs +++ b/crates/net/network/src/config.rs @@ -1,7 +1,6 @@ use crate::{peers::PeersConfig, session::SessionsConfig}; use reth_discv4::{Discv4Config, Discv4ConfigBuilder, DEFAULT_DISCOVERY_PORT}; -use reth_eth_wire::forkid::ForkId; -use reth_primitives::{Chain, H256}; +use reth_primitives::{Chain, ForkId, H256}; use secp256k1::SecretKey; use std::{ net::{Ipv4Addr, SocketAddr, SocketAddrV4}, @@ -76,8 +75,12 @@ pub struct NetworkConfigBuilder { peers_config: Option, /// How to configure the sessions manager sessions_config: Option, + /// A fork identifier as defined by EIP-2124. + /// Serves as the chain compatibility identifier. fork_id: Option, + /// The network's chain id chain: Chain, + /// Network genesis hash genesis_hash: H256, } diff --git a/crates/net/network/src/discovery.rs b/crates/net/network/src/discovery.rs index 65001938d..b2eb288f5 100644 --- a/crates/net/network/src/discovery.rs +++ b/crates/net/network/src/discovery.rs @@ -1,8 +1,9 @@ //! Discovery support for the network. -use crate::{error::NetworkError, NodeId}; +use crate::error::NetworkError; use futures::StreamExt; use reth_discv4::{Discv4, Discv4Config, NodeRecord, TableUpdate}; +use reth_primitives::PeerId; use secp256k1::SecretKey; use std::{ collections::{hash_map::Entry, HashMap, VecDeque}, @@ -19,7 +20,7 @@ pub struct Discovery { /// All nodes discovered via discovery protocol. /// /// These nodes can be ephemeral and are updated via the discovery protocol. - discovered_nodes: HashMap, + discovered_nodes: HashMap, /// Local ENR of the discovery service. local_enr: NodeRecord, /// Handler to interact with the Discovery v4 service @@ -66,12 +67,12 @@ impl Discovery { } /// Returns the id with which the local identifies itself in the network - pub(crate) fn local_id(&self) -> NodeId { + pub(crate) fn local_id(&self) -> PeerId { self.local_enr.id } /// Manually adds an address to the set. - pub(crate) fn add_known_address(&mut self, node_id: NodeId, addr: SocketAddr) { + pub(crate) fn add_known_address(&mut self, node_id: PeerId, addr: SocketAddr) { self.on_discv4_update(TableUpdate::Added(NodeRecord { address: addr.ip(), tcp_port: addr.port(), @@ -81,7 +82,7 @@ impl Discovery { } /// Returns all nodes we know exist in the network. - pub fn known_nodes(&mut self) -> &HashMap { + pub fn known_nodes(&mut self) -> &HashMap { &self.discovered_nodes } @@ -131,7 +132,7 @@ impl Discovery { /// Events produced by the [`Discovery`] manager. pub enum DiscoveryEvent { /// A new node was discovered - Discovered(NodeId, SocketAddr), + Discovered(PeerId, SocketAddr), } #[cfg(test)] diff --git a/crates/net/network/src/fetch.rs b/crates/net/network/src/fetch.rs index 7219681ff..c870b6149 100644 --- a/crates/net/network/src/fetch.rs +++ b/crates/net/network/src/fetch.rs @@ -1,10 +1,10 @@ //! Fetch data from the network. -use crate::{message::BlockRequest, NodeId}; +use crate::message::BlockRequest; use futures::StreamExt; use reth_eth_wire::{BlockBody, EthMessage}; use reth_interfaces::p2p::{error::RequestResult, headers::client::HeadersRequest}; -use reth_primitives::{Header, H256, U256}; +use reth_primitives::{Header, PeerId, H256, U256}; use std::{ collections::{HashMap, VecDeque}, task::{Context, Poll}, @@ -19,9 +19,9 @@ use tokio_stream::wrappers::UnboundedReceiverStream; /// peers and sends the response once ready. pub struct StateFetcher { /// Currently active [`GetBlockHeaders`] requests - inflight_headers_requests: HashMap>>>, + inflight_headers_requests: HashMap>>>, /// The list of available peers for requests. - peers: HashMap, + peers: HashMap, /// Requests queued for processing queued_requests: VecDeque, /// Receiver for new incoming download requests @@ -34,13 +34,13 @@ pub struct StateFetcher { impl StateFetcher { /// Invoked when connected to a new peer. - pub(crate) fn new_connected_peer(&mut self, _node_id: NodeId, _best_hash: H256) {} + pub(crate) fn new_connected_peer(&mut self, _node_id: PeerId, _best_hash: H256) {} /// Invoked when an active session was closed. - pub(crate) fn on_session_closed(&mut self, _peer: &NodeId) {} + pub(crate) fn on_session_closed(&mut self, _peer: &PeerId) {} /// Invoked when an active session is about to be disconnected. - pub(crate) fn on_pending_disconnect(&mut self, _peer: &NodeId) {} + pub(crate) fn on_pending_disconnect(&mut self, _peer: &PeerId) {} /// Returns the next action to return fn poll_action(&mut self) -> Option { @@ -94,7 +94,7 @@ impl StateFetcher { /// Called on a `GetBlockHeaders` response from a peer pub(crate) fn on_block_headers_response( &mut self, - _peer: NodeId, + _peer: PeerId, _res: RequestResult>, ) -> Option { None @@ -103,7 +103,7 @@ impl StateFetcher { /// Called on a `GetBlockBodies` response from a peer pub(crate) fn on_block_bodies_response( &mut self, - _peer: NodeId, + _peer: PeerId, _res: RequestResult>, ) -> Option { None @@ -189,7 +189,7 @@ enum DownloadRequest { pub(crate) enum FetchAction { /// Dispatch an eth request to the given peer. EthRequest { - node_id: NodeId, + node_id: PeerId, /// The request to send request: EthMessage, }, @@ -201,8 +201,8 @@ pub(crate) enum FetchAction { #[derive(Debug)] pub(crate) enum BlockResponseOutcome { /// Continue with another request to the peer. - Request(NodeId, BlockRequest), + Request(PeerId, BlockRequest), /// How to handle a bad response // TODO this should include some form of reputation change - BadResponse(NodeId), + BadResponse(PeerId), } diff --git a/crates/net/network/src/lib.rs b/crates/net/network/src/lib.rs index 05ad4c2d7..45b1c7097 100644 --- a/crates/net/network/src/lib.rs +++ b/crates/net/network/src/lib.rs @@ -5,7 +5,7 @@ attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) ))] // TODO remove later -#![allow(dead_code)] +#![allow(dead_code, clippy::too_many_arguments)] //! reth P2P networking. //! @@ -29,9 +29,6 @@ mod state; mod swarm; mod transactions; -/// Identifier for a unique node -pub type NodeId = reth_discv4::NodeId; - pub use config::NetworkConfig; pub use manager::NetworkManager; pub use network::NetworkHandle; diff --git a/crates/net/network/src/manager.rs b/crates/net/network/src/manager.rs index 9aa2b9290..340efe113 100644 --- a/crates/net/network/src/manager.rs +++ b/crates/net/network/src/manager.rs @@ -25,7 +25,6 @@ use crate::{ session::SessionManager, state::NetworkState, swarm::{Swarm, SwarmEvent}, - NodeId, }; use futures::{Future, StreamExt}; use parking_lot::Mutex; @@ -34,6 +33,7 @@ use reth_eth_wire::{ EthMessage, }; use reth_interfaces::provider::BlockProvider; +use reth_primitives::PeerId; use std::{ net::SocketAddr, pin::Pin, @@ -88,8 +88,8 @@ pub struct NetworkManager { /// This is updated via internal events and shared via `Arc` with the [`NetworkHandle`] /// Updated by the `NetworkWorker` and loaded by the `NetworkService`. num_active_peers: Arc, - /// Local copy of the `NodeId` of the local node. - local_node_id: NodeId, + /// Local copy of the `PeerId` of the local node. + local_node_id: PeerId, } // === impl NetworkManager === @@ -163,7 +163,7 @@ where /// Event hook for an unexpected message from the peer. fn on_invalid_message( &self, - node_id: NodeId, + node_id: PeerId, _capabilities: Arc, _message: CapabilityMessage, ) { @@ -172,7 +172,7 @@ where } /// Handles a received [`CapabilityMessage`] from the peer. - fn on_capability_message(&mut self, _node_id: NodeId, msg: CapabilityMessage) { + fn on_capability_message(&mut self, _node_id: PeerId, msg: CapabilityMessage) { match msg { CapabilityMessage::Eth(eth) => { match eth { @@ -299,7 +299,7 @@ where /// Events emitted by the network that are of interest for subscribers. #[derive(Debug, Clone)] pub enum NetworkEvent { - EthMessage { node_id: NodeId, message: EthMessage }, + EthMessage { node_id: PeerId, message: EthMessage }, } /// Bundles all listeners for [`NetworkEvent`]s. diff --git a/crates/net/network/src/message.rs b/crates/net/network/src/message.rs index 43dc28461..3aaaca3cc 100644 --- a/crates/net/network/src/message.rs +++ b/crates/net/network/src/message.rs @@ -11,10 +11,9 @@ use reth_eth_wire::{ }; use std::task::{ready, Context, Poll}; -use crate::NodeId; use reth_eth_wire::capability::CapabilityMessage; use reth_interfaces::p2p::error::RequestResult; -use reth_primitives::{Header, Receipt, TransactionSigned}; +use reth_primitives::{Header, PeerId, Receipt, TransactionSigned}; use tokio::sync::{mpsc, mpsc::error::TrySendError, oneshot}; /// Represents all messages that can be sent to a peer session @@ -180,7 +179,7 @@ impl PeerResponseResult { #[derive(Debug, Clone)] pub struct PeerRequestSender { /// id of the remote node. - pub(crate) peer: NodeId, + pub(crate) peer: PeerId, /// The Sender half connected to a session. pub(crate) to_session_tx: mpsc::Sender, } diff --git a/crates/net/network/src/network.rs b/crates/net/network/src/network.rs index 76b903460..c38986e94 100644 --- a/crates/net/network/src/network.rs +++ b/crates/net/network/src/network.rs @@ -1,6 +1,6 @@ -use crate::{manager::NetworkEvent, peers::PeersHandle, NodeId}; +use crate::{manager::NetworkEvent, peers::PeersHandle}; use parking_lot::Mutex; -use reth_primitives::{H256, U256}; +use reth_primitives::{PeerId, H256, U256}; use std::{ net::SocketAddr, sync::{atomic::AtomicUsize, Arc}, @@ -24,7 +24,7 @@ impl NetworkHandle { num_active_peers: Arc, listener_address: Arc>, to_manager_tx: UnboundedSender, - local_node_id: NodeId, + local_node_id: PeerId, peers: PeersHandle, ) -> Self { let inner = NetworkInner { @@ -57,7 +57,7 @@ struct NetworkInner { /// The local address that accepts incoming connections. listener_address: Arc>, /// The identifier used by this node. - local_node_id: NodeId, + local_node_id: PeerId, /// Access to the all the nodes peers: PeersHandle, // TODO need something to access } diff --git a/crates/net/network/src/peers.rs b/crates/net/network/src/peers.rs index d11954844..9592f215e 100644 --- a/crates/net/network/src/peers.rs +++ b/crates/net/network/src/peers.rs @@ -1,5 +1,5 @@ use futures::StreamExt; -use reth_discv4::NodeId; +use reth_primitives::PeerId; use std::{ collections::{hash_map::Entry, HashMap, VecDeque}, net::SocketAddr, @@ -32,7 +32,7 @@ pub struct PeersHandle { /// The [`PeersManager`] will be notified on peer related changes pub(crate) struct PeersManager { /// All peers known to the network - peers: HashMap, + peers: HashMap, /// Copy of the receiver half, so new [`PeersHandle`] can be created on demand. manager_tx: mpsc::UnboundedSender, /// Receiver half of the command channel. @@ -74,7 +74,7 @@ impl PeersManager { /// /// If the reputation of the peer is below the `BANNED_REPUTATION` threshold, a disconnect will /// be scheduled. - pub(crate) fn on_active_session(&mut self, peer_id: NodeId, addr: SocketAddr) { + pub(crate) fn on_active_session(&mut self, peer_id: PeerId, addr: SocketAddr) { match self.peers.entry(peer_id) { Entry::Occupied(mut entry) => { let value = entry.get_mut(); @@ -96,7 +96,7 @@ impl PeersManager { /// Called when a session to a peer was disconnected. /// /// Accepts an additional [`ReputationChange`] value to apply to the peer. - pub(crate) fn on_disconnected(&mut self, peer: NodeId, reputation_change: ReputationChange) { + pub(crate) fn on_disconnected(&mut self, peer: PeerId, reputation_change: ReputationChange) { if let Some(mut peer) = self.peers.get_mut(&peer) { self.connection_info.decr_state(peer.state); peer.state = PeerConnectionState::Idle; @@ -108,7 +108,7 @@ impl PeersManager { /// /// If the peer already exists, then the address will e updated. If the addresses differ, the /// old address is returned - pub(crate) fn add_discovered_node(&mut self, peer_id: NodeId, addr: SocketAddr) { + pub(crate) fn add_discovered_node(&mut self, peer_id: PeerId, addr: SocketAddr) { match self.peers.entry(peer_id) { Entry::Occupied(mut entry) => { let node = entry.get_mut(); @@ -121,7 +121,7 @@ impl PeersManager { } /// Removes the tracked node from the set. - pub(crate) fn remove_discovered_node(&mut self, peer_id: NodeId) { + pub(crate) fn remove_discovered_node(&mut self, peer_id: PeerId) { if let Some(entry) = self.peers.remove(&peer_id) { if entry.state.is_connected() { self.connection_info.decr_state(entry.state); @@ -133,11 +133,11 @@ impl PeersManager { /// Returns the idle peer with the highest reputation. /// /// Returns `None` if no peer is available. - fn best_unconnected(&mut self) -> Option<(NodeId, &mut Peer)> { + fn best_unconnected(&mut self) -> Option<(PeerId, &mut Peer)> { self.peers .iter_mut() .filter(|(_, peer)| peer.state.is_unconnected()) - .fold(None::<(&NodeId, &mut Peer)>, |mut best_peer, candidate| { + .fold(None::<(&PeerId, &mut Peer)>, |mut best_peer, candidate| { if let Some(best_peer) = best_peer.take() { if best_peer.1.reputation >= candidate.1.reputation { return Some(best_peer) @@ -331,14 +331,14 @@ pub(crate) enum PeerCommand { /// Command for manually add Add { /// Identifier of the peer. - peer_id: NodeId, + peer_id: PeerId, /// The address of the peer addr: SocketAddr, }, /// Remove a peer from the set /// /// If currently connected this will disconnect the sessin - Remove(NodeId), + Remove(PeerId), } /// Actions the peer manager can trigger. @@ -347,17 +347,17 @@ pub enum PeerAction { /// Start a new connection to a peer. Connect { /// The peer to connect to. - peer_id: NodeId, + peer_id: PeerId, /// Where to reach the node remote_addr: SocketAddr, }, /// Disconnect an existing connection. - Disconnect { peer_id: NodeId }, + Disconnect { peer_id: PeerId }, /// Disconnect an existing incoming connection, because the peers reputation is below the /// banned threshold. DisconnectBannedIncoming { /// Peer id of the established connection. - peer_id: NodeId, + peer_id: PeerId, }, } diff --git a/crates/net/network/src/session/active.rs b/crates/net/network/src/session/active.rs index 91db0ef2c..a8a1519dd 100644 --- a/crates/net/network/src/session/active.rs +++ b/crates/net/network/src/session/active.rs @@ -6,13 +6,16 @@ use crate::{ handle::{ActiveSessionMessage, SessionCommand}, SessionId, }, - NodeId, }; use fnv::FnvHashMap; use futures::{stream::Fuse, Sink, Stream}; use pin_project::pin_project; use reth_ecies::stream::ECIESStream; -use reth_eth_wire::capability::{Capabilities, CapabilityMessage}; +use reth_eth_wire::{ + capability::{Capabilities, CapabilityMessage}, + EthStream, P2PStream, +}; +use reth_primitives::PeerId; use std::{ collections::VecDeque, future::Future, @@ -31,9 +34,9 @@ pub(crate) struct ActiveSession { pub(crate) next_id: usize, /// The underlying connection. #[pin] - pub(crate) conn: ECIESStream, + pub(crate) conn: EthStream>>, /// Identifier of the node we're connected to. - pub(crate) remote_node_id: NodeId, + pub(crate) remote_node_id: PeerId, /// All capabilities the peer announced pub(crate) remote_capabilities: Arc, /// Internal identifier of this session diff --git a/crates/net/network/src/session/handle.rs b/crates/net/network/src/session/handle.rs index fce7b5d03..f7cd579ca 100644 --- a/crates/net/network/src/session/handle.rs +++ b/crates/net/network/src/session/handle.rs @@ -1,13 +1,12 @@ //! Session handles -use crate::{ - session::{Direction, SessionId}, - NodeId, -}; +use crate::session::{Direction, SessionId}; use reth_ecies::{stream::ECIESStream, ECIESError}; use reth_eth_wire::{ capability::{Capabilities, CapabilityMessage}, - Status, + error::EthStreamError, + EthStream, P2PStream, Status, }; +use reth_primitives::PeerId; use std::{io, net::SocketAddr, sync::Arc, time::Instant}; use tokio::{ net::TcpStream, @@ -33,7 +32,7 @@ pub(crate) struct ActiveSessionHandle { /// The assigned id for this session pub(crate) session_id: SessionId, /// The identifier of the remote peer - pub(crate) remote_id: NodeId, + pub(crate) remote_id: PeerId, /// The timestamp when the session has been established. pub(crate) established: Instant, /// Announced capabilities of the peer. @@ -65,23 +64,24 @@ pub(crate) enum PendingSessionEvent { Established { session_id: SessionId, remote_addr: SocketAddr, - node_id: NodeId, + /// The remote node's public key + node_id: PeerId, capabilities: Arc, status: Status, - conn: ECIESStream, + conn: EthStream>>, }, /// Handshake unsuccessful, session was disconnected. Disconnected { remote_addr: SocketAddr, session_id: SessionId, direction: Direction, - error: Option, + error: Option, }, /// Thrown when unable to establish a [`TcpStream`]. OutgoingConnectionError { remote_addr: SocketAddr, session_id: SessionId, - node_id: NodeId, + node_id: PeerId, error: io::Error, }, /// Thrown when authentication via Ecies failed. @@ -101,18 +101,18 @@ pub(crate) enum SessionCommand { #[derive(Debug)] pub(crate) enum ActiveSessionMessage { /// Session disconnected. - Closed { node_id: NodeId, remote_addr: SocketAddr }, + Closed { node_id: PeerId, remote_addr: SocketAddr }, /// A session received a valid message via RLPx. ValidMessage { /// Identifier of the remote peer. - node_id: NodeId, + node_id: PeerId, /// Message received from the peer. message: CapabilityMessage, }, /// Received a message that does not match the announced capabilities of the peer. InvalidMessage { /// Identifier of the remote peer. - node_id: NodeId, + node_id: PeerId, /// Announced capabilities of the remote peer. capabilities: Arc, /// Message received from the peer. diff --git a/crates/net/network/src/session/mod.rs b/crates/net/network/src/session/mod.rs index aa92fab76..c3faecba2 100644 --- a/crates/net/network/src/session/mod.rs +++ b/crates/net/network/src/session/mod.rs @@ -1,21 +1,20 @@ //! Support for handling peer sessions. pub use crate::message::PeerRequestSender; -use crate::{ - session::{ - active::ActiveSession, - handle::{ - ActiveSessionHandle, ActiveSessionMessage, PendingSessionEvent, PendingSessionHandle, - }, +use crate::session::{ + active::ActiveSession, + handle::{ + ActiveSessionHandle, ActiveSessionMessage, PendingSessionEvent, PendingSessionHandle, }, - NodeId, }; use fnv::FnvHashMap; use futures::{future::Either, io, FutureExt, StreamExt}; -use reth_ecies::{stream::ECIESStream, ECIESError}; +use reth_ecies::stream::ECIESStream; use reth_eth_wire::{ capability::{Capabilities, CapabilityMessage}, - Status, UnauthedEthStream, + error::EthStreamError, + HelloBuilder, HelloMessage, Status, StatusBuilder, UnauthedEthStream, UnauthedP2PStream, }; +use reth_primitives::{ForkFilter, Hardfork, PeerId}; use secp256k1::{SecretKey, SECP256K1}; use std::{ collections::HashMap, @@ -48,7 +47,13 @@ pub(crate) struct SessionManager { /// The secret key used for authenticating sessions. secret_key: SecretKey, /// The node id of node - node_id: NodeId, + node_id: PeerId, + /// The `Status` message to send to peers. + status: Status, + /// THe `Hello` message to send to peers. + hello: HelloMessage, + /// The [`ForkFilter`] used to validate the peer's `Status` message. + fork_filter: ForkFilter, /// Size of the command buffer per session. session_command_buffer: usize, /// All spawned session tasks. @@ -61,7 +66,7 @@ pub(crate) struct SessionManager { /// session is authenticated, it can be moved to the `active_session` set. pending_sessions: FnvHashMap, /// All active sessions that are ready to exchange messages. - active_sessions: HashMap, + active_sessions: HashMap, /// The original Sender half of the [`PendingSessionEvent`] channel. /// /// When a new (pending) session is created, the corresponding [`PendingSessionHandle`] will @@ -87,12 +92,21 @@ impl SessionManager { let (active_session_tx, active_session_rx) = mpsc::channel(config.session_event_buffer); let pk = secret_key.public_key(SECP256K1); - let node_id = NodeId::from_slice(&pk.serialize_uncompressed()[1..]); + let node_id = PeerId::from_slice(&pk.serialize_uncompressed()[1..]); + + // TODO: make sure this is the right place to put these builders - maybe per-Network rather + // than per-Session? + let hello = HelloBuilder::new(node_id).build(); + let status = StatusBuilder::default().build(); + let fork_filter = Hardfork::Frontier.fork_filter(); Self { next_id: 0, secret_key, node_id, + status, + hello, + fork_filter, session_command_buffer: config.session_command_buffer, spawned_tasks: Default::default(), pending_sessions: Default::default(), @@ -139,6 +153,9 @@ impl SessionManager { pending_events, remote_addr, self.secret_key, + self.hello.clone(), + self.status, + self.fork_filter.clone(), )); let handle = PendingSessionHandle { disconnect_tx }; @@ -147,7 +164,7 @@ impl SessionManager { } /// Starts a new pending session from the local node to the given remote node. - pub(crate) fn dial_outbound(&mut self, remote_addr: SocketAddr, remote_node_id: NodeId) { + pub(crate) fn dial_outbound(&mut self, remote_addr: SocketAddr, remote_node_id: PeerId) { let session_id = self.next_id(); let (disconnect_tx, disconnect_rx) = oneshot::channel(); let pending_events = self.pending_sessions_tx.clone(); @@ -158,6 +175,9 @@ impl SessionManager { remote_addr, remote_node_id, self.secret_key, + self.hello.clone(), + self.status, + self.fork_filter.clone(), )); let handle = PendingSessionHandle { disconnect_tx }; @@ -168,7 +188,7 @@ impl SessionManager { /// /// This will trigger the disconnect on the session task to gracefully terminate. The result /// will be picked up by the receiver. - pub(crate) fn disconnect(&self, node: NodeId) { + pub(crate) fn disconnect(&self, node: PeerId) { if let Some(session) = self.active_sessions.get(&node) { session.disconnect(); } @@ -376,7 +396,7 @@ pub(crate) enum SessionEvent { /// /// This session is now able to exchange data. SessionEstablished { - node_id: NodeId, + node_id: PeerId, remote_addr: SocketAddr, capabilities: Arc, status: Status, @@ -384,30 +404,30 @@ pub(crate) enum SessionEvent { }, /// A session received a valid message via RLPx. ValidMessage { - node_id: NodeId, + node_id: PeerId, /// Message received from the peer. message: CapabilityMessage, }, /// Received a message that does not match the announced capabilities of the peer. InvalidMessage { - node_id: NodeId, + node_id: PeerId, /// Announced capabilities of the remote peer. capabilities: Arc, /// Message received from the peer. message: CapabilityMessage, }, /// Closed an incoming pending session during authentication. - IncomingPendingSessionClosed { remote_addr: SocketAddr, error: Option }, + IncomingPendingSessionClosed { remote_addr: SocketAddr, error: Option }, /// Closed an outgoing pending session during authentication. OutgoingPendingSessionClosed { remote_addr: SocketAddr, - node_id: NodeId, - error: Option, + node_id: PeerId, + error: Option, }, /// Failed to establish a tcp stream - OutgoingConnectionError { remote_addr: SocketAddr, node_id: NodeId, error: io::Error }, + OutgoingConnectionError { remote_addr: SocketAddr, node_id: PeerId, error: io::Error }, /// Active session was disconnected. - Disconnected { node_id: NodeId, remote_addr: SocketAddr }, + Disconnected { node_id: PeerId, remote_addr: SocketAddr }, } /// The error thrown when the max configured limit has been reached and no more connections are @@ -426,6 +446,9 @@ async fn start_pending_incoming_session( events: mpsc::Sender, remote_addr: SocketAddr, secret_key: SecretKey, + hello: HelloMessage, + status: Status, + fork_filter: ForkFilter, ) { authenticate( disconnect_rx, @@ -435,6 +458,9 @@ async fn start_pending_incoming_session( remote_addr, secret_key, Direction::Incoming, + hello, + status, + fork_filter, ) .await } @@ -446,8 +472,11 @@ async fn start_pending_outbound_session( events: mpsc::Sender, session_id: SessionId, remote_addr: SocketAddr, - remote_node_id: NodeId, + remote_node_id: PeerId, secret_key: SecretKey, + hello: HelloMessage, + status: Status, + fork_filter: ForkFilter, ) { let stream = match TcpStream::connect(remote_addr).await { Ok(stream) => stream, @@ -471,6 +500,9 @@ async fn start_pending_outbound_session( remote_addr, secret_key, Direction::Outgoing(remote_node_id), + hello, + status, + fork_filter, ) .await } @@ -481,7 +513,7 @@ pub(crate) enum Direction { /// Incoming connection. Incoming, /// Outgoing connection to a specific node. - Outgoing(NodeId), + Outgoing(PeerId), } async fn authenticate( @@ -492,6 +524,9 @@ async fn authenticate( remote_addr: SocketAddr, secret_key: SecretKey, direction: Direction, + hello: HelloMessage, + status: Status, + fork_filter: ForkFilter, ) { let stream = match direction { Direction::Incoming => match ECIESStream::incoming(stream, secret_key).await { @@ -520,8 +555,17 @@ async fn authenticate( } }; - let unauthed = UnauthedEthStream::new(stream); - let auth = authenticate_stream(unauthed, session_id, remote_addr, direction).boxed(); + let unauthed = UnauthedP2PStream::new(stream); + let auth = authenticate_stream( + unauthed, + session_id, + remote_addr, + direction, + hello, + status, + fork_filter, + ) + .boxed(); match futures::future::select(disconnect_rx, auth).await { Either::Left((_, _)) => { @@ -544,10 +588,47 @@ async fn authenticate( /// /// On Success return the authenticated stream as [`PendingSessionEvent`] async fn authenticate_stream( - _stream: UnauthedEthStream>, - _session_id: SessionId, - _remote_addr: SocketAddr, - _direction: Direction, + stream: UnauthedP2PStream>, + session_id: SessionId, + remote_addr: SocketAddr, + direction: Direction, + hello: HelloMessage, + status: Status, + fork_filter: ForkFilter, ) -> PendingSessionEvent { - todo!() + // conduct the p2p handshake and return the authenticated stream + let (p2p_stream, their_hello) = match stream.handshake(hello).await { + Ok(stream_res) => stream_res, + Err(err) => { + return PendingSessionEvent::Disconnected { + remote_addr, + session_id, + direction, + error: Some(err.into()), + } + } + }; + + // if the hello handshake was successful we can try status handshake + let eth_unauthed = UnauthedEthStream::new(p2p_stream); + let (eth_stream, their_status) = match eth_unauthed.handshake(status, fork_filter).await { + Ok(stream_res) => stream_res, + Err(err) => { + return PendingSessionEvent::Disconnected { + remote_addr, + session_id, + direction, + error: Some(err), + } + } + }; + + PendingSessionEvent::Established { + session_id, + remote_addr, + node_id: their_hello.id, + capabilities: Arc::new(Capabilities::from(their_hello.capabilities)), + status: their_status, + conn: eth_stream, + } } diff --git a/crates/net/network/src/state.rs b/crates/net/network/src/state.rs index 30e0eac43..b2404c084 100644 --- a/crates/net/network/src/state.rs +++ b/crates/net/network/src/state.rs @@ -5,12 +5,11 @@ use crate::{ fetch::StateFetcher, message::{PeerRequestSender, PeerResponse}, peers::{PeerAction, PeersManager}, - NodeId, }; use reth_eth_wire::{capability::Capabilities, Status}; use reth_interfaces::provider::BlockProvider; -use reth_primitives::H256; +use reth_primitives::{PeerId, H256}; use std::{ collections::{HashMap, VecDeque}, net::SocketAddr, @@ -37,7 +36,7 @@ use tracing::trace; /// This type is also responsible for responding for received request. pub struct NetworkState { /// All connected peers and their state. - connected_peers: HashMap, + connected_peers: HashMap, /// Manages connections to peers. peers_manager: PeersManager, /// Buffered messages until polled. @@ -83,7 +82,7 @@ where /// should be rejected. pub(crate) fn on_session_activated( &mut self, - peer: NodeId, + peer: PeerId, capabilities: Arc, status: Status, request_tx: PeerRequestSender, @@ -107,7 +106,7 @@ where } /// Event hook for a disconnected session for the peer. - pub(crate) fn on_session_closed(&mut self, peer: NodeId) { + pub(crate) fn on_session_closed(&mut self, peer: PeerId) { self.connected_peers.remove(&peer); self.state_fetcher.on_session_closed(&peer); } @@ -149,7 +148,7 @@ where } /// Disconnect the session - fn on_session_disconnected(&mut self, peer: NodeId) { + fn on_session_disconnected(&mut self, peer: PeerId) { self.connected_peers.remove(&peer); } @@ -157,7 +156,7 @@ where /// /// Caution: this will replace an already pending response. It's the responsibility of the /// caller to select the peer. - fn handle_block_request(&mut self, peer: NodeId, request: BlockRequest) { + fn handle_block_request(&mut self, peer: PeerId, request: BlockRequest) { if let Some(ref mut peer) = self.connected_peers.get_mut(&peer) { let (request, response) = match request { BlockRequest::GetBlockHeaders(request) => { @@ -192,7 +191,7 @@ where } /// Invoked when received a response from a connected peer. - fn on_eth_response(&mut self, peer: NodeId, resp: PeerResponseResult) -> Option { + fn on_eth_response(&mut self, peer: PeerId, resp: PeerResponseResult) -> Option { match resp { PeerResponseResult::BlockHeaders(res) => { let outcome = self.state_fetcher.on_block_headers_response(peer, res)?; @@ -283,9 +282,9 @@ pub struct ConnectedPeer { /// Message variants triggered by the [`State`] pub enum StateAction { /// Create a new connection to the given node. - Connect { remote_addr: SocketAddr, node_id: NodeId }, + Connect { remote_addr: SocketAddr, node_id: PeerId }, /// Disconnect an existing connection - Disconnect { node_id: NodeId }, + Disconnect { node_id: PeerId }, } #[derive(Debug, thiserror::Error)] @@ -293,6 +292,6 @@ pub enum AddSessionError { #[error("No capacity for new sessions")] AtCapacity { /// The peer of the session - peer: NodeId, + peer: PeerId, }, } diff --git a/crates/net/network/src/swarm.rs b/crates/net/network/src/swarm.rs index e62195613..136f11abd 100644 --- a/crates/net/network/src/swarm.rs +++ b/crates/net/network/src/swarm.rs @@ -2,12 +2,14 @@ use crate::{ listener::{ConnectionListener, ListenerEvent}, session::{SessionEvent, SessionId, SessionManager}, state::{AddSessionError, NetworkState, StateAction}, - NodeId, }; use futures::Stream; -use reth_ecies::ECIESError; -use reth_eth_wire::capability::{Capabilities, CapabilityMessage}; +use reth_eth_wire::{ + capability::{Capabilities, CapabilityMessage}, + error::EthStreamError, +}; use reth_interfaces::provider::BlockProvider; +use reth_primitives::PeerId; use std::{ io, net::SocketAddr, @@ -55,7 +57,7 @@ where } /// Triggers a new outgoing connection to the given node - pub(crate) fn dial_outbound(&mut self, remote_addr: SocketAddr, remote_id: NodeId) { + pub(crate) fn dial_outbound(&mut self, remote_addr: SocketAddr, remote_id: PeerId) { self.sessions.dial_outbound(remote_addr, remote_id) } @@ -191,13 +193,13 @@ pub enum SwarmEvent { /// Events related to the actual network protocol. CapabilityMessage { /// The peer that sent the message - node_id: NodeId, + node_id: PeerId, /// Message received from the peer message: CapabilityMessage, }, /// Received a message that does not match the announced capabilities of the peer. InvalidCapabilityMessage { - node_id: NodeId, + node_id: PeerId, /// Announced capabilities of the remote peer. capabilities: Arc, /// Message received from the peer. @@ -226,28 +228,28 @@ pub enum SwarmEvent { remote_addr: SocketAddr, }, SessionEstablished { - node_id: NodeId, + node_id: PeerId, remote_addr: SocketAddr, }, SessionClosed { - node_id: NodeId, + node_id: PeerId, remote_addr: SocketAddr, }, /// Closed an incoming pending session during authentication. IncomingPendingSessionClosed { remote_addr: SocketAddr, - error: Option, + error: Option, }, /// Closed an outgoing pending session during authentication. OutgoingPendingSessionClosed { remote_addr: SocketAddr, - node_id: NodeId, - error: Option, + node_id: PeerId, + error: Option, }, /// Failed to establish a tcp stream to the given address/node OutgoingConnectionError { remote_addr: SocketAddr, - node_id: NodeId, + node_id: PeerId, error: io::Error, }, } diff --git a/crates/primitives/src/constants.rs b/crates/primitives/src/constants.rs new file mode 100644 index 000000000..d51732f45 --- /dev/null +++ b/crates/primitives/src/constants.rs @@ -0,0 +1,5 @@ +use crate::H256; + +/// The Ethereum mainnet genesis hash. +pub const MAINNET_GENESIS: H256 = + H256(hex_literal::hex!("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")); diff --git a/crates/net/eth-wire/src/types/forkid.rs b/crates/primitives/src/forkid.rs similarity index 99% rename from crates/net/eth-wire/src/types/forkid.rs rename to crates/primitives/src/forkid.rs index 77b9ad950..5a11de104 100644 --- a/crates/net/eth-wire/src/types/forkid.rs +++ b/crates/primitives/src/forkid.rs @@ -3,9 +3,9 @@ #![deny(missing_docs)] #![allow(clippy::redundant_else, clippy::too_many_lines)] +use crate::{BlockNumber, H256}; use crc::crc32; use maplit::btreemap; -use reth_primitives::{BlockNumber, H256}; use reth_rlp::*; use std::{ collections::{BTreeMap, BTreeSet}, diff --git a/crates/primitives/src/hardfork.rs b/crates/primitives/src/hardfork.rs new file mode 100644 index 000000000..32ffbce34 --- /dev/null +++ b/crates/primitives/src/hardfork.rs @@ -0,0 +1,225 @@ +use crate::{BlockNumber, ForkFilter, ForkHash, ForkId, MAINNET_GENESIS}; +use std::str::FromStr; + +/// Ethereum mainnet hardforks +#[allow(missing_docs)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum Hardfork { + Frontier, + Homestead, + Dao, + Tangerine, + SpuriousDragon, + Byzantium, + Constantinople, + Petersburg, + Istanbul, + Muirglacier, + Berlin, + London, + ArrowGlacier, + GrayGlacier, + Latest, +} + +impl Hardfork { + /// Get the first block number of the hardfork. + pub fn fork_block(&self) -> u64 { + match *self { + Hardfork::Frontier => 0, + Hardfork::Homestead => 1150000, + Hardfork::Dao => 1920000, + Hardfork::Tangerine => 2463000, + Hardfork::SpuriousDragon => 2675000, + Hardfork::Byzantium => 4370000, + Hardfork::Constantinople | Hardfork::Petersburg => 7280000, + Hardfork::Istanbul => 9069000, + Hardfork::Muirglacier => 9200000, + Hardfork::Berlin => 12244000, + Hardfork::London => 12965000, + Hardfork::ArrowGlacier => 13773000, + Hardfork::GrayGlacier | Hardfork::Latest => 15050000, + } + } + + /// Get the EIP-2124 fork id for a given hardfork + /// + /// The [`ForkId`](ethereum_forkid::ForkId) includes a CRC32 checksum of the all fork block + /// numbers from genesis, and the next upcoming fork block number. + /// If the next fork block number is not yet known, it is set to 0. + pub fn fork_id(&self) -> ForkId { + match *self { + Hardfork::Frontier => { + ForkId { hash: ForkHash([0xfc, 0x64, 0xec, 0x04]), next: 1150000 } + } + Hardfork::Homestead => { + ForkId { hash: ForkHash([0x97, 0xc2, 0xc3, 0x4c]), next: 1920000 } + } + Hardfork::Dao => ForkId { hash: ForkHash([0x91, 0xd1, 0xf9, 0x48]), next: 2463000 }, + Hardfork::Tangerine => { + ForkId { hash: ForkHash([0x7a, 0x64, 0xda, 0x13]), next: 2675000 } + } + Hardfork::SpuriousDragon => { + ForkId { hash: ForkHash([0x3e, 0xdd, 0x5b, 0x10]), next: 4370000 } + } + Hardfork::Byzantium => { + ForkId { hash: ForkHash([0xa0, 0x0b, 0xc3, 0x24]), next: 7280000 } + } + Hardfork::Constantinople | Hardfork::Petersburg => { + ForkId { hash: ForkHash([0x66, 0x8d, 0xb0, 0xaf]), next: 9069000 } + } + Hardfork::Istanbul => { + ForkId { hash: ForkHash([0x87, 0x9d, 0x6e, 0x30]), next: 9200000 } + } + Hardfork::Muirglacier => { + ForkId { hash: ForkHash([0xe0, 0x29, 0xe9, 0x91]), next: 12244000 } + } + Hardfork::Berlin => ForkId { hash: ForkHash([0x0e, 0xb4, 0x40, 0xf6]), next: 12965000 }, + Hardfork::London => ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 13773000 }, + Hardfork::ArrowGlacier => { + ForkId { hash: ForkHash([0x20, 0xc3, 0x27, 0xfc]), next: 15050000 } + } + Hardfork::Latest | Hardfork::GrayGlacier => { + // update `next` when another fork block num is known + ForkId { hash: ForkHash([0xf0, 0xaf, 0xd0, 0xe3]), next: 0 } + } + } + } + + /// This returns all known hardforks in order. + pub fn all_forks() -> Vec { + vec![ + Hardfork::Homestead, + Hardfork::Dao, + Hardfork::Tangerine, + Hardfork::SpuriousDragon, + Hardfork::Byzantium, + Hardfork::Constantinople, /* petersburg is skipped because it's the same block num + * as constantinople */ + Hardfork::Istanbul, + Hardfork::Muirglacier, + Hardfork::Berlin, + Hardfork::London, + Hardfork::ArrowGlacier, + Hardfork::GrayGlacier, + ] + } + + /// This returns all known hardfork block numbers as a vector. + pub fn all_fork_blocks() -> Vec { + Hardfork::all_forks().iter().map(|f| f.fork_block()).collect() + } + + /// Creates a [`ForkFilter`](crate::ForkFilter) for the given hardfork. + /// This assumes the current hardfork's block number is the current head and uses all known + /// future hardforks to initialize the filter. + pub fn fork_filter(&self) -> ForkFilter { + let all_forks = Hardfork::all_forks(); + let future_forks: Vec = all_forks + .iter() + .filter(|f| f.fork_block() > self.fork_block()) + .map(|f| f.fork_block()) + .collect(); + + // this data structure is not chain-agnostic, so we can pass in the constant mainnet + // genesis + ForkFilter::new(self.fork_block(), MAINNET_GENESIS, future_forks) + } +} + +impl FromStr for Hardfork { + type Err = String; + + fn from_str(s: &str) -> Result { + let s = s.to_lowercase(); + let hardfork = match s.as_str() { + "frontier" | "1" => Hardfork::Frontier, + "homestead" | "2" => Hardfork::Homestead, + "dao" | "3" => Hardfork::Dao, + "tangerine" | "4" => Hardfork::Tangerine, + "spuriousdragon" | "5" => Hardfork::SpuriousDragon, + "byzantium" | "6" => Hardfork::Byzantium, + "constantinople" | "7" => Hardfork::Constantinople, + "petersburg" | "8" => Hardfork::Petersburg, + "istanbul" | "9" => Hardfork::Istanbul, + "muirglacier" | "10" => Hardfork::Muirglacier, + "berlin" | "11" => Hardfork::Berlin, + "london" | "12" => Hardfork::London, + "arrowglacier" | "13" => Hardfork::ArrowGlacier, + "grayglacier" => Hardfork::GrayGlacier, + "latest" | "14" => Hardfork::Latest, + _ => return Err(format!("Unknown hardfork {s}")), + }; + Ok(hardfork) + } +} + +impl Default for Hardfork { + fn default() -> Self { + Hardfork::Latest + } +} + +impl From for Hardfork { + fn from(num: BlockNumber) -> Hardfork { + match num { + _i if num < 1_150_000 => Hardfork::Frontier, + _i if num < 1_920_000 => Hardfork::Dao, + _i if num < 2_463_000 => Hardfork::Homestead, + _i if num < 2_675_000 => Hardfork::Tangerine, + _i if num < 4_370_000 => Hardfork::SpuriousDragon, + _i if num < 7_280_000 => Hardfork::Byzantium, + _i if num < 9_069_000 => Hardfork::Constantinople, + _i if num < 9_200_000 => Hardfork::Istanbul, + _i if num < 12_244_000 => Hardfork::Muirglacier, + _i if num < 12_965_000 => Hardfork::Berlin, + _i if num < 13_773_000 => Hardfork::London, + _i if num < 15_050_000 => Hardfork::ArrowGlacier, + + _ => Hardfork::Latest, + } + } +} + +#[cfg(test)] +mod tests { + use crate::{forkid::ForkHash, hardfork::Hardfork}; + use crc::crc32; + + #[test] + fn test_hardfork_blocks() { + let hf: Hardfork = 12_965_000u64.into(); + assert_eq!(hf, Hardfork::London); + + let hf: Hardfork = 4370000u64.into(); + assert_eq!(hf, Hardfork::Byzantium); + + let hf: Hardfork = 12244000u64.into(); + assert_eq!(hf, Hardfork::Berlin); + } + + #[test] + // this test checks that the fork hash assigned to forks accurately map to the fork_id method + fn test_forkhash_from_fork_blocks() { + // set the genesis hash + let genesis = + hex::decode("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") + .unwrap(); + + // set the frontier forkhash + let mut curr_forkhash = ForkHash(crc32::checksum_ieee(&genesis[..]).to_be_bytes()); + + // now we go through enum members + let frontier_forkid = Hardfork::Frontier.fork_id(); + assert_eq!(curr_forkhash, frontier_forkid.hash); + + // list of the above hardforks + let hardforks = Hardfork::all_forks(); + + // check that the curr_forkhash we compute matches the output of each fork_id returned + for hardfork in hardforks { + curr_forkhash += hardfork.fork_block(); + assert_eq!(curr_forkhash, hardfork.fork_id().hash); + } + } +} diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 0cf932a41..d70f9253a 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -10,7 +10,10 @@ mod account; mod block; mod chain; +mod constants; mod error; +mod forkid; +mod hardfork; mod header; mod hex_bytes; mod integer_list; @@ -23,6 +26,9 @@ mod transaction; pub use account::Account; pub use block::{Block, BlockLocked}; pub use chain::Chain; +pub use constants::MAINNET_GENESIS; +pub use forkid::{ForkFilter, ForkHash, ForkId, ValidationError}; +pub use hardfork::Hardfork; pub use header::{Header, SealedHeader}; pub use hex_bytes::Bytes; pub use integer_list::IntegerList; @@ -56,6 +62,12 @@ pub type StorageKey = H256; /// Storage value pub type StorageValue = U256; +// TODO: should we use `PublicKey` for this? Even when dealing with public keys we should try to +// prevent misuse +/// This represents an uncompressed secp256k1 public key. +/// This encodes the concatenation of the x and y components of the affine point in bytes. +pub type PeerId = H512; + pub use ethers_core::{ types as rpc, types::{BigEndianHash, Bloom, H128, H160, H256, H512, H64, U128, U256, U64},