mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
Add serde support for NodeRecord primitive type (#617)
* Add serde support to NodeRecord * Move NodeRecord to primitives along with NodeKey and Octets * Reexport NodeRecord from discv4 * Move NodeKey and kad_key back to discv4::node Also, move NodeRecord::key functionality to a helper function: discv4::node::record_key. This avoids the discv5 dependency in the primitives crate. * Fix NodeRecord (de)serializing The default derive macros work with a dictionary like display. Changed that to serde_with macros, that use Display and FromStr traits. * Add some tests for NodeRecord (de)serializing * Hide NodeKey struct * Move Octets after NodeRecord * Replace record_key with From trait * Fix clippy error unnecessary into()
This commit is contained in:
156
Cargo.lock
generated
156
Cargo.lock
generated
@ -80,6 +80,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anes"
|
||||
version = "0.1.6"
|
||||
@ -446,8 +455,11 @@ version = "0.4.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -582,6 +594,16 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
|
||||
dependencies = [
|
||||
"termcolor",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "confy"
|
||||
version = "0.5.1"
|
||||
@ -784,6 +806,50 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxxbridge-flags",
|
||||
"cxxbridge-macro",
|
||||
"link-cplusplus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx-build"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"codespan-reporting",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"scratch",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-flags"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59"
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-macro"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.10.2"
|
||||
@ -1912,6 +1978,30 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71a816c97c42258aa5834d07590b718b4c9a598944cd39a52dc25b351185d678"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
|
||||
dependencies = [
|
||||
"cxx",
|
||||
"cxx-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
@ -2014,6 +2104,7 @@ checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2323,6 +2414,15 @@ dependencies = [
|
||||
"bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "link-cplusplus"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.6"
|
||||
@ -3426,7 +3526,6 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3689,16 +3788,20 @@ dependencies = [
|
||||
"modular-bitfield",
|
||||
"parity-scale-codec",
|
||||
"plain_hasher",
|
||||
"rand 0.8.5",
|
||||
"reth-codecs",
|
||||
"reth-rlp",
|
||||
"reth-rlp-derive",
|
||||
"secp256k1 0.24.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"sucds",
|
||||
"test-fuzz",
|
||||
"thiserror",
|
||||
"tiny-keccak",
|
||||
"triehash",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4108,6 +4211,12 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "scratch"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2"
|
||||
|
||||
[[package]]
|
||||
name = "sct"
|
||||
version = "0.7.0"
|
||||
@ -4274,6 +4383,34 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25bf4a5a814902cd1014dbccfa4d4560fb8432c779471e96e035602519f82eef"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"chrono",
|
||||
"hex",
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with_macros",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3452b4c0f6c1e357f73fdb87cd1efabaa12acf328c7a528e252893baeb3f4aa"
|
||||
dependencies = [
|
||||
"darling 0.14.2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serial_test"
|
||||
version = "0.10.0"
|
||||
@ -4721,8 +4858,10 @@ version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4731,6 +4870,15 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"
|
||||
dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
@ -5113,6 +5261,12 @@ dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.4"
|
||||
|
||||
@ -31,13 +31,12 @@ tokio-stream = "0.1"
|
||||
|
||||
# misc
|
||||
bytes = "1.2"
|
||||
generic-array = "0.14"
|
||||
tracing = "0.1"
|
||||
thiserror = "1.0"
|
||||
url = "2.3"
|
||||
hex = "0.4"
|
||||
public-ip = "0.2"
|
||||
rand = { version = "0.8", optional = true }
|
||||
generic-array = "0.14"
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8"
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
// <https://github.com/ledgerwatch/erigon/blob/610e648dc43ec8cd6563313e28f06f534a9091b3/params/bootnodes.go>
|
||||
|
||||
use crate::node::NodeRecord;
|
||||
use reth_primitives::NodeRecord;
|
||||
|
||||
/// Ethereum Foundation Go Bootnodes
|
||||
pub static MAINNET_BOOTNODES : [&str; 8] = [
|
||||
|
||||
@ -3,9 +3,9 @@
|
||||
//! This basis of this file has been taken from the discv5 codebase:
|
||||
//! https://github.com/sigp/discv5
|
||||
|
||||
use crate::node::NodeRecord;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use reth_net_common::ban_list::BanList;
|
||||
use reth_primitives::NodeRecord;
|
||||
use reth_rlp::Encodable;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
|
||||
@ -19,7 +19,6 @@
|
||||
//! [`DiscoveryUpdate`] that listeners will receive.
|
||||
use crate::{
|
||||
error::{DecodePacketError, Discv4Error},
|
||||
node::{kad_key, NodeKey},
|
||||
proto::{FindNode, Message, Neighbours, Packet, Ping, Pong},
|
||||
};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
@ -58,8 +57,12 @@ mod proto;
|
||||
|
||||
mod config;
|
||||
pub use config::{Discv4Config, Discv4ConfigBuilder};
|
||||
|
||||
mod node;
|
||||
pub use node::NodeRecord;
|
||||
use node::{kad_key, NodeKey};
|
||||
|
||||
// reexport NodeRecord primitive
|
||||
pub use reth_primitives::NodeRecord;
|
||||
|
||||
#[cfg(any(test, feature = "mock"))]
|
||||
pub mod mock;
|
||||
@ -139,8 +142,8 @@ impl Discv4 {
|
||||
/// use std::str::FromStr;
|
||||
/// use rand::thread_rng;
|
||||
/// use secp256k1::SECP256K1;
|
||||
/// use reth_primitives::PeerId;
|
||||
/// use reth_discv4::{Discv4, Discv4Config, NodeRecord};
|
||||
/// use reth_primitives::{NodeRecord, PeerId};
|
||||
/// use reth_discv4::{Discv4, Discv4Config};
|
||||
/// # async fn t() -> io::Result<()> {
|
||||
/// // generate a (random) keypair
|
||||
/// let mut rng = thread_rng();
|
||||
@ -384,7 +387,7 @@ impl Discv4Service {
|
||||
tasks.spawn(async move { send_loop(udp, egress_rx).await });
|
||||
|
||||
let kbuckets = KBucketsTable::new(
|
||||
local_node_record.key(),
|
||||
NodeKey::from(&local_node_record).into(),
|
||||
Duration::from_secs(60),
|
||||
MAX_NODES_PER_BUCKET,
|
||||
None,
|
||||
@ -1831,7 +1834,7 @@ mod tests {
|
||||
fn test_insert() {
|
||||
let local_node_record = rng_record(&mut rand::thread_rng());
|
||||
let mut kbuckets: KBucketsTable<NodeKey, NodeEntry> = KBucketsTable::new(
|
||||
local_node_record.key(),
|
||||
NodeKey::from(&local_node_record).into(),
|
||||
Duration::from_secs(60),
|
||||
MAX_NODES_PER_BUCKET,
|
||||
None,
|
||||
|
||||
@ -3,13 +3,12 @@
|
||||
#![allow(missing_docs, unused)]
|
||||
|
||||
use crate::{
|
||||
node::NodeRecord,
|
||||
proto::{FindNode, Message, Neighbours, NodeEndpoint, Packet, Ping, Pong},
|
||||
receive_loop, send_loop, Discv4, Discv4Config, Discv4Service, EgressSender, IngressEvent,
|
||||
IngressReceiver, PeerId, SAFE_MAX_DATAGRAM_NEIGHBOUR_RECORDS,
|
||||
};
|
||||
use rand::{thread_rng, Rng, RngCore};
|
||||
use reth_primitives::{hex_literal::hex, ForkHash, ForkId, H256};
|
||||
use reth_primitives::{hex_literal::hex, ForkHash, ForkId, NodeRecord, H256};
|
||||
use secp256k1::{SecretKey, SECP256K1};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
|
||||
@ -1,18 +1,5 @@
|
||||
use crate::{proto::Octets, PeerId};
|
||||
use bytes::{Buf, BufMut};
|
||||
use generic_array::GenericArray;
|
||||
use reth_primitives::keccak256;
|
||||
use reth_rlp::{Decodable, DecodeError, Encodable};
|
||||
use reth_rlp_derive::RlpEncodable;
|
||||
use secp256k1::{SecretKey, SECP256K1};
|
||||
use std::{
|
||||
fmt,
|
||||
fmt::Write,
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
num::ParseIntError,
|
||||
str::FromStr,
|
||||
};
|
||||
use url::{Host, Url};
|
||||
use reth_primitives::{keccak256, NodeRecord, PeerId};
|
||||
|
||||
/// The key type for the table.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
@ -32,252 +19,14 @@ impl From<NodeKey> for discv5::Key<NodeKey> {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&NodeRecord> for NodeKey {
|
||||
fn from(node: &NodeRecord) -> Self {
|
||||
NodeKey(node.id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a `PeerId` into the required `Key` type for the table
|
||||
#[inline]
|
||||
pub(crate) fn kad_key(node: PeerId) -> discv5::Key<NodeKey> {
|
||||
discv5::kbucket::Key::from(NodeKey::from(node))
|
||||
}
|
||||
|
||||
/// Represents a ENR in discv4.
|
||||
///
|
||||
/// Note: this is only an excerpt of the [ENR](enr::Enr) datastructure which is sent in Neighbours
|
||||
/// message.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct NodeRecord {
|
||||
/// The Address of a node.
|
||||
pub address: IpAddr,
|
||||
/// TCP port of the port that accepts connections.
|
||||
pub tcp_port: u16,
|
||||
/// UDP discovery port.
|
||||
pub udp_port: u16,
|
||||
/// Public key of the discovery service
|
||||
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 = PeerId::from_slice(&pk.serialize_uncompressed()[1..]);
|
||||
Self::new(addr, id)
|
||||
}
|
||||
|
||||
/// Creates a new record from a socket addr and peer id.
|
||||
#[allow(unused)]
|
||||
pub fn new(addr: SocketAddr, id: PeerId) -> Self {
|
||||
Self { address: addr.ip(), tcp_port: addr.port(), udp_port: addr.port(), id }
|
||||
}
|
||||
|
||||
/// The TCP socket address of this node
|
||||
#[must_use]
|
||||
pub fn tcp_addr(&self) -> SocketAddr {
|
||||
SocketAddr::new(self.address, self.tcp_port)
|
||||
}
|
||||
|
||||
/// The UDP socket address of this node
|
||||
#[must_use]
|
||||
pub fn udp_addr(&self) -> SocketAddr {
|
||||
SocketAddr::new(self.address, self.udp_port)
|
||||
}
|
||||
|
||||
/// Returns the key type for the kademlia table
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub(crate) fn key(&self) -> discv5::Key<NodeKey> {
|
||||
NodeKey(self.id).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NodeRecord {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("enode://")?;
|
||||
hex::encode(self.id.as_bytes()).fmt(f)?;
|
||||
f.write_char('@')?;
|
||||
self.address.fmt(f)?;
|
||||
f.write_char(':')?;
|
||||
self.tcp_port.fmt(f)?;
|
||||
if self.tcp_port != self.udp_port {
|
||||
f.write_str("?discport=")?;
|
||||
self.udp_port.fmt(f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Possible error types when parsing a `NodeRecord`
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum NodeRecordParseError {
|
||||
#[error("Failed to parse url: {0}")]
|
||||
InvalidUrl(String),
|
||||
#[error("Failed to parse id")]
|
||||
InvalidId(String),
|
||||
#[error("Failed to discport query: {0}")]
|
||||
Discport(ParseIntError),
|
||||
}
|
||||
|
||||
impl FromStr for NodeRecord {
|
||||
type Err = NodeRecordParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let url = Url::parse(s).map_err(|e| NodeRecordParseError::InvalidUrl(e.to_string()))?;
|
||||
|
||||
let address = match url.host() {
|
||||
Some(Host::Ipv4(ip)) => IpAddr::V4(ip),
|
||||
Some(Host::Ipv6(ip)) => IpAddr::V6(ip),
|
||||
Some(Host::Domain(ip)) => IpAddr::V4(
|
||||
Ipv4Addr::from_str(ip)
|
||||
.map_err(|e| NodeRecordParseError::InvalidUrl(e.to_string()))?,
|
||||
),
|
||||
_ => return Err(NodeRecordParseError::InvalidUrl(format!("invalid host: {url:?}"))),
|
||||
};
|
||||
let port = url
|
||||
.port()
|
||||
.ok_or_else(|| NodeRecordParseError::InvalidUrl("no port specified".to_string()))?;
|
||||
|
||||
let udp_port = if let Some(discovery_port) =
|
||||
url.query_pairs().find_map(|(maybe_disc, port)| {
|
||||
if maybe_disc.as_ref() == "discport" {
|
||||
Some(port)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
discovery_port.parse::<u16>().map_err(NodeRecordParseError::Discport)?
|
||||
} else {
|
||||
port
|
||||
};
|
||||
|
||||
let id = url
|
||||
.username()
|
||||
.parse::<PeerId>()
|
||||
.map_err(|e| NodeRecordParseError::InvalidId(e.to_string()))?;
|
||||
|
||||
Ok(Self { address, id, tcp_port: port, udp_port })
|
||||
}
|
||||
}
|
||||
|
||||
impl Encodable for NodeRecord {
|
||||
fn encode(&self, out: &mut dyn BufMut) {
|
||||
#[derive(RlpEncodable)]
|
||||
struct EncodeNode {
|
||||
octets: Octets,
|
||||
udp_port: u16,
|
||||
tcp_port: u16,
|
||||
id: PeerId,
|
||||
}
|
||||
|
||||
let octets = match self.address {
|
||||
IpAddr::V4(addr) => Octets::V4(addr.octets()),
|
||||
IpAddr::V6(addr) => Octets::V6(addr.octets()),
|
||||
};
|
||||
let node =
|
||||
EncodeNode { octets, udp_port: self.udp_port, tcp_port: self.tcp_port, id: self.id };
|
||||
node.encode(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for NodeRecord {
|
||||
fn decode(buf: &mut &[u8]) -> Result<Self, DecodeError> {
|
||||
let b = &mut &**buf;
|
||||
let rlp_head = reth_rlp::Header::decode(b)?;
|
||||
if !rlp_head.list {
|
||||
return Err(DecodeError::UnexpectedString)
|
||||
}
|
||||
let started_len = b.len();
|
||||
let octets = Octets::decode(b)?;
|
||||
let this = Self {
|
||||
address: octets.into(),
|
||||
udp_port: Decodable::decode(b)?,
|
||||
tcp_port: Decodable::decode(b)?,
|
||||
id: Decodable::decode(b)?,
|
||||
};
|
||||
// the ENR record can contain additional entries that we skip
|
||||
let consumed = started_len - b.len();
|
||||
if consumed > rlp_head.payload_length {
|
||||
return Err(DecodeError::ListLengthMismatch {
|
||||
expected: rlp_head.payload_length,
|
||||
got: consumed,
|
||||
})
|
||||
}
|
||||
let rem = rlp_head.payload_length - consumed;
|
||||
b.advance(rem);
|
||||
*buf = *b;
|
||||
Ok(this)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bytes::BytesMut;
|
||||
use rand::{thread_rng, Rng, RngCore};
|
||||
|
||||
#[test]
|
||||
fn test_noderecord_codec_ipv4() {
|
||||
let mut rng = thread_rng();
|
||||
for _ in 0..100 {
|
||||
let mut ip = [0u8; 4];
|
||||
rng.fill_bytes(&mut ip);
|
||||
let record = NodeRecord {
|
||||
address: IpAddr::V4(ip.into()),
|
||||
tcp_port: rng.gen(),
|
||||
udp_port: rng.gen(),
|
||||
id: PeerId::random(),
|
||||
};
|
||||
|
||||
let mut buf = BytesMut::new();
|
||||
record.encode(&mut buf);
|
||||
|
||||
let decoded = NodeRecord::decode(&mut buf.as_ref()).unwrap();
|
||||
assert_eq!(record, decoded);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_noderecord_codec_ipv6() {
|
||||
let mut rng = thread_rng();
|
||||
for _ in 0..100 {
|
||||
let mut ip = [0u8; 16];
|
||||
rng.fill_bytes(&mut ip);
|
||||
let record = NodeRecord {
|
||||
address: IpAddr::V6(ip.into()),
|
||||
tcp_port: rng.gen(),
|
||||
udp_port: rng.gen(),
|
||||
id: PeerId::random(),
|
||||
};
|
||||
|
||||
let mut buf = BytesMut::new();
|
||||
record.encode(&mut buf);
|
||||
|
||||
let decoded = NodeRecord::decode(&mut buf.as_ref()).unwrap();
|
||||
assert_eq!(record, decoded);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_url_parse() {
|
||||
let url = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301";
|
||||
let node: NodeRecord = url.parse().unwrap();
|
||||
assert_eq!(node, NodeRecord {
|
||||
address: IpAddr::V4([10,3,58,6].into()),
|
||||
tcp_port: 30303,
|
||||
udp_port: 30301,
|
||||
id: "6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0".parse().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_node_display() {
|
||||
let url = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303";
|
||||
let node: NodeRecord = url.parse().unwrap();
|
||||
assert_eq!(url, &format!("{node}"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_node_display_discport() {
|
||||
let url = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301";
|
||||
let node: NodeRecord = url.parse().unwrap();
|
||||
assert_eq!(url, &format!("{node}"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use crate::{error::DecodePacketError, node::NodeRecord, PeerId, MAX_PACKET_SIZE, MIN_PACKET_SIZE};
|
||||
use crate::{error::DecodePacketError, PeerId, MAX_PACKET_SIZE, MIN_PACKET_SIZE};
|
||||
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||
use enr::Enr;
|
||||
use reth_primitives::{keccak256, ForkId, H256};
|
||||
use reth_primitives::{keccak256, ForkId, NodeRecord, Octets, H256};
|
||||
use reth_rlp::{Decodable, DecodeError, Encodable, Header};
|
||||
use reth_rlp_derive::{RlpDecodable, RlpEncodable};
|
||||
use secp256k1::{
|
||||
ecdsa::{RecoverableSignature, RecoveryId},
|
||||
SecretKey, SECP256K1,
|
||||
};
|
||||
use std::net::{IpAddr, Ipv6Addr};
|
||||
use std::net::IpAddr;
|
||||
|
||||
// Note: this is adapted from https://github.com/vorot93/discv4
|
||||
|
||||
@ -459,64 +459,6 @@ impl Decodable for Pong {
|
||||
}
|
||||
}
|
||||
|
||||
/// IpAddr octets
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) enum Octets {
|
||||
V4([u8; 4]),
|
||||
V6([u8; 16]),
|
||||
}
|
||||
|
||||
impl From<Octets> for IpAddr {
|
||||
fn from(value: Octets) -> Self {
|
||||
match value {
|
||||
Octets::V4(o) => IpAddr::from(o),
|
||||
Octets::V6(o) => {
|
||||
let ipv6 = Ipv6Addr::from(o);
|
||||
// If the ipv6 is ipv4 compatible/mapped, simply return the ipv4.
|
||||
if let Some(ipv4) = ipv6.to_ipv4() {
|
||||
IpAddr::V4(ipv4)
|
||||
} else {
|
||||
IpAddr::V6(ipv6)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encodable for Octets {
|
||||
fn encode(&self, out: &mut dyn BufMut) {
|
||||
let octets = match self {
|
||||
Octets::V4(ref o) => &o[..],
|
||||
Octets::V6(ref o) => &o[..],
|
||||
};
|
||||
octets.encode(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for Octets {
|
||||
fn decode(buf: &mut &[u8]) -> Result<Self, DecodeError> {
|
||||
let h = Header::decode(buf)?;
|
||||
if h.list {
|
||||
return Err(DecodeError::UnexpectedList)
|
||||
}
|
||||
let o = match h.payload_length {
|
||||
4 => {
|
||||
let mut to = [0_u8; 4];
|
||||
to.copy_from_slice(&buf[..4]);
|
||||
Octets::V4(to)
|
||||
}
|
||||
16 => {
|
||||
let mut to = [0u8; 16];
|
||||
to.copy_from_slice(&buf[..16]);
|
||||
Octets::V6(to)
|
||||
}
|
||||
_ => return Err(DecodeError::UnexpectedLength),
|
||||
};
|
||||
buf.advance(h.payload_length);
|
||||
Ok(o)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@ -5,8 +5,8 @@ use crate::{
|
||||
peers::PeersConfig,
|
||||
session::SessionsConfig,
|
||||
};
|
||||
use reth_discv4::{Discv4Config, Discv4ConfigBuilder, NodeRecord, DEFAULT_DISCOVERY_PORT};
|
||||
use reth_primitives::{Chain, ForkFilter, Hardfork, PeerId, H256, MAINNET_GENESIS};
|
||||
use reth_discv4::{Discv4Config, Discv4ConfigBuilder, DEFAULT_DISCOVERY_PORT};
|
||||
use reth_primitives::{Chain, ForkFilter, Hardfork, NodeRecord, PeerId, H256, MAINNET_GENESIS};
|
||||
use reth_tasks::TaskExecutor;
|
||||
use secp256k1::{SecretKey, SECP256K1};
|
||||
use std::{
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
use crate::error::NetworkError;
|
||||
use futures::StreamExt;
|
||||
use reth_discv4::{DiscoveryUpdate, Discv4, Discv4Config, NodeRecord};
|
||||
use reth_primitives::{ForkId, PeerId};
|
||||
use reth_discv4::{DiscoveryUpdate, Discv4, Discv4Config};
|
||||
use reth_primitives::{ForkId, NodeRecord, PeerId};
|
||||
use secp256k1::SecretKey;
|
||||
use std::{
|
||||
collections::{hash_map::Entry, HashMap, VecDeque},
|
||||
|
||||
@ -7,11 +7,11 @@ use enr::{k256::ecdsa::SigningKey, Enr, EnrPublicKey};
|
||||
use ethers_core::utils::Geth;
|
||||
use ethers_providers::{Http, Middleware, Provider};
|
||||
use futures::StreamExt;
|
||||
use reth_discv4::{bootnodes::mainnet_nodes, Discv4Config, NodeRecord};
|
||||
use reth_discv4::{bootnodes::mainnet_nodes, Discv4Config};
|
||||
use reth_eth_wire::DisconnectReason;
|
||||
use reth_net_common::ban_list::BanList;
|
||||
use reth_network::{NetworkConfig, NetworkEvent, NetworkManager, PeersConfig};
|
||||
use reth_primitives::PeerId;
|
||||
use reth_primitives::{NodeRecord, PeerId};
|
||||
use reth_provider::test_utils::TestApi;
|
||||
use secp256k1::SecretKey;
|
||||
use std::{collections::HashSet, net::SocketAddr, sync::Arc, time::Duration};
|
||||
|
||||
@ -14,6 +14,7 @@ reth-rlp = { path = "../common/rlp", features = [
|
||||
"derive",
|
||||
"ethereum-types",
|
||||
] }
|
||||
reth-rlp-derive = { path = "../common/rlp-derive" }
|
||||
reth-codecs = { version = "0.1.0", path = "../storage/codecs" }
|
||||
|
||||
# ethereum
|
||||
@ -34,6 +35,7 @@ crc = "1"
|
||||
# misc
|
||||
bytes = "1.2"
|
||||
serde = "1.0"
|
||||
serde_with = "2.1.0"
|
||||
thiserror = "1"
|
||||
sucds = "0.5.0"
|
||||
arbitrary = { version = "1.1.7", features = ["derive"], optional = true }
|
||||
@ -41,6 +43,7 @@ hex = "0.4"
|
||||
hex-literal = "0.3"
|
||||
modular-bitfield = "0.11.2"
|
||||
derive_more = "0.99"
|
||||
url = "2.3"
|
||||
|
||||
# proof related
|
||||
triehash = "0.8"
|
||||
@ -53,6 +56,7 @@ arbitrary = { version = "1.1.7", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
hex-literal = "0.3"
|
||||
test-fuzz = "3.0.4"
|
||||
rand = "0.8"
|
||||
|
||||
# necessary so we don't hit a "undeclared 'std'":
|
||||
# https://github.com/paradigmxyz/reth/pull/177#discussion_r1021172198
|
||||
|
||||
@ -22,6 +22,7 @@ mod hex_bytes;
|
||||
mod integer_list;
|
||||
mod jsonu256;
|
||||
mod log;
|
||||
mod net;
|
||||
mod peer;
|
||||
mod receipt;
|
||||
mod storage;
|
||||
@ -42,6 +43,7 @@ pub use hex_bytes::Bytes;
|
||||
pub use integer_list::IntegerList;
|
||||
pub use jsonu256::JsonU256;
|
||||
pub use log::Log;
|
||||
pub use net::{NodeRecord, Octets};
|
||||
pub use peer::{PeerId, WithPeerId};
|
||||
pub use receipt::Receipt;
|
||||
pub use storage::StorageEntry;
|
||||
|
||||
335
crates/primitives/src/net.rs
Normal file
335
crates/primitives/src/net.rs
Normal file
@ -0,0 +1,335 @@
|
||||
use crate::PeerId;
|
||||
use bytes::{Buf, BufMut};
|
||||
use reth_rlp::{Decodable, DecodeError, Encodable, Header};
|
||||
use reth_rlp_derive::RlpEncodable;
|
||||
use secp256k1::{SecretKey, SECP256K1};
|
||||
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||
use std::{
|
||||
fmt,
|
||||
fmt::Write,
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
||||
num::ParseIntError,
|
||||
str::FromStr,
|
||||
};
|
||||
use url::{Host, Url};
|
||||
|
||||
/// Represents a ENR in discv4.
|
||||
///
|
||||
/// Note: this is only an excerpt of the [ENR](enr::Enr) datastructure which is sent in Neighbours
|
||||
/// message.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, SerializeDisplay, DeserializeFromStr)]
|
||||
pub struct NodeRecord {
|
||||
/// The Address of a node.
|
||||
pub address: IpAddr,
|
||||
/// TCP port of the port that accepts connections.
|
||||
pub tcp_port: u16,
|
||||
/// UDP discovery port.
|
||||
pub udp_port: u16,
|
||||
/// Public key of the discovery service
|
||||
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 = PeerId::from_slice(&pk.serialize_uncompressed()[1..]);
|
||||
Self::new(addr, id)
|
||||
}
|
||||
|
||||
/// Creates a new record from a socket addr and peer id.
|
||||
#[allow(unused)]
|
||||
pub fn new(addr: SocketAddr, id: PeerId) -> Self {
|
||||
Self { address: addr.ip(), tcp_port: addr.port(), udp_port: addr.port(), id }
|
||||
}
|
||||
|
||||
/// The TCP socket address of this node
|
||||
#[must_use]
|
||||
pub fn tcp_addr(&self) -> SocketAddr {
|
||||
SocketAddr::new(self.address, self.tcp_port)
|
||||
}
|
||||
|
||||
/// The UDP socket address of this node
|
||||
#[must_use]
|
||||
pub fn udp_addr(&self) -> SocketAddr {
|
||||
SocketAddr::new(self.address, self.udp_port)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NodeRecord {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("enode://")?;
|
||||
hex::encode(self.id.as_bytes()).fmt(f)?;
|
||||
f.write_char('@')?;
|
||||
self.address.fmt(f)?;
|
||||
f.write_char(':')?;
|
||||
self.tcp_port.fmt(f)?;
|
||||
if self.tcp_port != self.udp_port {
|
||||
f.write_str("?discport=")?;
|
||||
self.udp_port.fmt(f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Possible error types when parsing a `NodeRecord`
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum NodeRecordParseError {
|
||||
#[error("Failed to parse url: {0}")]
|
||||
InvalidUrl(String),
|
||||
#[error("Failed to parse id")]
|
||||
InvalidId(String),
|
||||
#[error("Failed to discport query: {0}")]
|
||||
Discport(ParseIntError),
|
||||
}
|
||||
|
||||
impl FromStr for NodeRecord {
|
||||
type Err = NodeRecordParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let url = Url::parse(s).map_err(|e| NodeRecordParseError::InvalidUrl(e.to_string()))?;
|
||||
|
||||
let address = match url.host() {
|
||||
Some(Host::Ipv4(ip)) => IpAddr::V4(ip),
|
||||
Some(Host::Ipv6(ip)) => IpAddr::V6(ip),
|
||||
Some(Host::Domain(ip)) => IpAddr::V4(
|
||||
Ipv4Addr::from_str(ip)
|
||||
.map_err(|e| NodeRecordParseError::InvalidUrl(e.to_string()))?,
|
||||
),
|
||||
_ => return Err(NodeRecordParseError::InvalidUrl(format!("invalid host: {url:?}"))),
|
||||
};
|
||||
let port = url
|
||||
.port()
|
||||
.ok_or_else(|| NodeRecordParseError::InvalidUrl("no port specified".to_string()))?;
|
||||
|
||||
let udp_port = if let Some(discovery_port) =
|
||||
url.query_pairs().find_map(|(maybe_disc, port)| {
|
||||
if maybe_disc.as_ref() == "discport" {
|
||||
Some(port)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
discovery_port.parse::<u16>().map_err(NodeRecordParseError::Discport)?
|
||||
} else {
|
||||
port
|
||||
};
|
||||
|
||||
let id = url
|
||||
.username()
|
||||
.parse::<PeerId>()
|
||||
.map_err(|e| NodeRecordParseError::InvalidId(e.to_string()))?;
|
||||
|
||||
Ok(Self { address, id, tcp_port: port, udp_port })
|
||||
}
|
||||
}
|
||||
|
||||
impl Encodable for NodeRecord {
|
||||
fn encode(&self, out: &mut dyn BufMut) {
|
||||
#[derive(RlpEncodable)]
|
||||
struct EncodeNode {
|
||||
octets: Octets,
|
||||
udp_port: u16,
|
||||
tcp_port: u16,
|
||||
id: PeerId,
|
||||
}
|
||||
|
||||
let octets = match self.address {
|
||||
IpAddr::V4(addr) => Octets::V4(addr.octets()),
|
||||
IpAddr::V6(addr) => Octets::V6(addr.octets()),
|
||||
};
|
||||
let node =
|
||||
EncodeNode { octets, udp_port: self.udp_port, tcp_port: self.tcp_port, id: self.id };
|
||||
node.encode(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for NodeRecord {
|
||||
fn decode(buf: &mut &[u8]) -> Result<Self, DecodeError> {
|
||||
let b = &mut &**buf;
|
||||
let rlp_head = reth_rlp::Header::decode(b)?;
|
||||
if !rlp_head.list {
|
||||
return Err(DecodeError::UnexpectedString)
|
||||
}
|
||||
let started_len = b.len();
|
||||
let octets = Octets::decode(b)?;
|
||||
let this = Self {
|
||||
address: octets.into(),
|
||||
udp_port: Decodable::decode(b)?,
|
||||
tcp_port: Decodable::decode(b)?,
|
||||
id: Decodable::decode(b)?,
|
||||
};
|
||||
// the ENR record can contain additional entries that we skip
|
||||
let consumed = started_len - b.len();
|
||||
if consumed > rlp_head.payload_length {
|
||||
return Err(DecodeError::ListLengthMismatch {
|
||||
expected: rlp_head.payload_length,
|
||||
got: consumed,
|
||||
})
|
||||
}
|
||||
let rem = rlp_head.payload_length - consumed;
|
||||
b.advance(rem);
|
||||
*buf = *b;
|
||||
Ok(this)
|
||||
}
|
||||
}
|
||||
|
||||
/// IpAddr octets
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Octets {
|
||||
/// Ipv4 Octet variant
|
||||
V4([u8; 4]),
|
||||
/// Ipv6 Octet variant
|
||||
V6([u8; 16]),
|
||||
}
|
||||
|
||||
impl From<Octets> for IpAddr {
|
||||
fn from(value: Octets) -> Self {
|
||||
match value {
|
||||
Octets::V4(o) => IpAddr::from(o),
|
||||
Octets::V6(o) => {
|
||||
let ipv6 = Ipv6Addr::from(o);
|
||||
// If the ipv6 is ipv4 compatible/mapped, simply return the ipv4.
|
||||
if let Some(ipv4) = ipv6.to_ipv4() {
|
||||
IpAddr::V4(ipv4)
|
||||
} else {
|
||||
IpAddr::V6(ipv6)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encodable for Octets {
|
||||
fn encode(&self, out: &mut dyn BufMut) {
|
||||
let octets = match self {
|
||||
Octets::V4(ref o) => &o[..],
|
||||
Octets::V6(ref o) => &o[..],
|
||||
};
|
||||
octets.encode(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for Octets {
|
||||
fn decode(buf: &mut &[u8]) -> Result<Self, DecodeError> {
|
||||
let h = Header::decode(buf)?;
|
||||
if h.list {
|
||||
return Err(DecodeError::UnexpectedList)
|
||||
}
|
||||
let o = match h.payload_length {
|
||||
4 => {
|
||||
let mut to = [0_u8; 4];
|
||||
to.copy_from_slice(&buf[..4]);
|
||||
Octets::V4(to)
|
||||
}
|
||||
16 => {
|
||||
let mut to = [0u8; 16];
|
||||
to.copy_from_slice(&buf[..16]);
|
||||
Octets::V6(to)
|
||||
}
|
||||
_ => return Err(DecodeError::UnexpectedLength),
|
||||
};
|
||||
buf.advance(h.payload_length);
|
||||
Ok(o)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bytes::BytesMut;
|
||||
use rand::{thread_rng, Rng, RngCore};
|
||||
|
||||
#[test]
|
||||
fn test_noderecord_codec_ipv4() {
|
||||
let mut rng = thread_rng();
|
||||
for _ in 0..100 {
|
||||
let mut ip = [0u8; 4];
|
||||
rng.fill_bytes(&mut ip);
|
||||
let record = NodeRecord {
|
||||
address: IpAddr::V4(ip.into()),
|
||||
tcp_port: rng.gen(),
|
||||
udp_port: rng.gen(),
|
||||
id: PeerId::random(),
|
||||
};
|
||||
|
||||
let mut buf = BytesMut::new();
|
||||
record.encode(&mut buf);
|
||||
|
||||
let decoded = NodeRecord::decode(&mut buf.as_ref()).unwrap();
|
||||
assert_eq!(record, decoded);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_noderecord_codec_ipv6() {
|
||||
let mut rng = thread_rng();
|
||||
for _ in 0..100 {
|
||||
let mut ip = [0u8; 16];
|
||||
rng.fill_bytes(&mut ip);
|
||||
let record = NodeRecord {
|
||||
address: IpAddr::V6(ip.into()),
|
||||
tcp_port: rng.gen(),
|
||||
udp_port: rng.gen(),
|
||||
id: PeerId::random(),
|
||||
};
|
||||
|
||||
let mut buf = BytesMut::new();
|
||||
record.encode(&mut buf);
|
||||
|
||||
let decoded = NodeRecord::decode(&mut buf.as_ref()).unwrap();
|
||||
assert_eq!(record, decoded);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_url_parse() {
|
||||
let url = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301";
|
||||
let node: NodeRecord = url.parse().unwrap();
|
||||
assert_eq!(node, NodeRecord {
|
||||
address: IpAddr::V4([10,3,58,6].into()),
|
||||
tcp_port: 30303,
|
||||
udp_port: 30301,
|
||||
id: "6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0".parse().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_node_display() {
|
||||
let url = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303";
|
||||
let node: NodeRecord = url.parse().unwrap();
|
||||
assert_eq!(url, &format!("{node}"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_node_display_discport() {
|
||||
let url = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301";
|
||||
let node: NodeRecord = url.parse().unwrap();
|
||||
assert_eq!(url, &format!("{node}"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_node_serialize() {
|
||||
let node = NodeRecord{
|
||||
address: IpAddr::V4([10, 3, 58, 6].into()),
|
||||
tcp_port: 30303u16,
|
||||
udp_port: 30301u16,
|
||||
id: PeerId::from_str("6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0").unwrap(),
|
||||
};
|
||||
let ser = serde_json::to_string::<NodeRecord>(&node).expect("couldn't serialize");
|
||||
assert_eq!(ser, "\"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301\"")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_node_deserialize() {
|
||||
let url = "\"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301\"";
|
||||
let node: NodeRecord = serde_json::from_str(url).expect("couldn't deserialize");
|
||||
assert_eq!(node, NodeRecord{
|
||||
address: IpAddr::V4([10, 3, 58, 6].into()),
|
||||
tcp_port: 30303u16,
|
||||
udp_port: 30301u16,
|
||||
id: PeerId::from_str("6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0").unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user