mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
fix: make discv4 packets adhere to eip-8 (#8039)
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -6583,6 +6583,7 @@ name = "reth-discv4"
|
|||||||
version = "0.2.0-beta.6"
|
version = "0.2.0-beta.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-rlp",
|
"alloy-rlp",
|
||||||
|
"assert_matches",
|
||||||
"discv5",
|
"discv5",
|
||||||
"enr",
|
"enr",
|
||||||
"generic-array",
|
"generic-array",
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
use crate::Head;
|
use crate::Head;
|
||||||
use alloy_primitives::{hex, BlockNumber, B256};
|
use alloy_primitives::{hex, BlockNumber, B256};
|
||||||
use alloy_rlp::*;
|
use alloy_rlp::{Error as RlpError, *};
|
||||||
#[cfg(any(test, feature = "arbitrary"))]
|
#[cfg(any(test, feature = "arbitrary"))]
|
||||||
use arbitrary::Arbitrary;
|
use arbitrary::Arbitrary;
|
||||||
use crc::*;
|
use crc::*;
|
||||||
@ -116,19 +116,51 @@ pub struct ForkId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a forward-compatible ENR entry for including the forkid in a node record via
|
/// Represents a forward-compatible ENR entry for including the forkid in a node record via
|
||||||
/// EIP-868. Forward compatibility is achieved by allowing trailing fields.
|
/// EIP-868. Forward compatibility is achieved via EIP-8.
|
||||||
///
|
///
|
||||||
/// See:
|
/// See:
|
||||||
/// <https://github.com/ethereum/devp2p/blob/master/enr-entries/eth.md#entry-format>
|
/// <https://github.com/ethereum/devp2p/blob/master/enr-entries/eth.md#entry-format>
|
||||||
///
|
///
|
||||||
/// for how geth implements ForkId values and forward compatibility.
|
/// for how geth implements ForkId values and forward compatibility.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
|
#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable)]
|
||||||
#[rlp(trailing)]
|
|
||||||
pub struct EnrForkIdEntry {
|
pub struct EnrForkIdEntry {
|
||||||
/// The inner forkid
|
/// The inner forkid
|
||||||
pub fork_id: ForkId,
|
pub fork_id: ForkId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Decodable for EnrForkIdEntry {
|
||||||
|
// NOTE(onbjerg): Manual implementation to satisfy EIP-8.
|
||||||
|
//
|
||||||
|
// See https://eips.ethereum.org/EIPS/eip-8
|
||||||
|
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
|
||||||
|
let b = &mut &**buf;
|
||||||
|
let rlp_head = Header::decode(b)?;
|
||||||
|
if !rlp_head.list {
|
||||||
|
return Err(RlpError::UnexpectedString)
|
||||||
|
}
|
||||||
|
let started_len = b.len();
|
||||||
|
|
||||||
|
let this = Self { fork_id: Decodable::decode(b)? };
|
||||||
|
|
||||||
|
// NOTE(onbjerg): Because of EIP-8, we only check that we did not consume *more* than the
|
||||||
|
// payload length, i.e. it is ok if payload length is greater than what we consumed, as we
|
||||||
|
// just discard the remaining list items
|
||||||
|
let consumed = started_len - b.len();
|
||||||
|
if consumed > rlp_head.payload_length {
|
||||||
|
return Err(RlpError::ListLengthMismatch {
|
||||||
|
expected: rlp_head.payload_length,
|
||||||
|
got: consumed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let rem = rlp_head.payload_length - consumed;
|
||||||
|
b.advance(rem);
|
||||||
|
*buf = *b;
|
||||||
|
|
||||||
|
Ok(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<ForkId> for EnrForkIdEntry {
|
impl From<ForkId> for EnrForkIdEntry {
|
||||||
fn from(fork_id: ForkId) -> Self {
|
fn from(fork_id: ForkId) -> Self {
|
||||||
Self { fork_id }
|
Self { fork_id }
|
||||||
@ -652,4 +684,39 @@ mod tests {
|
|||||||
assert!(fork_filter.set_head_priv(Head { number: b2, ..Default::default() }).is_some());
|
assert!(fork_filter.set_head_priv(Head { number: b2, ..Default::default() }).is_some());
|
||||||
assert_eq!(fork_filter.current(), h2);
|
assert_eq!(fork_filter.current(), h2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod eip8 {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn junk_enr_fork_id_entry() -> Vec<u8> {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
// enr request is just an expiration
|
||||||
|
let fork_id = ForkId { hash: ForkHash(hex!("deadbeef")), next: 0xBADDCAFE };
|
||||||
|
|
||||||
|
// add some junk
|
||||||
|
let junk: u64 = 112233;
|
||||||
|
|
||||||
|
// rlp header encoding
|
||||||
|
let payload_length = fork_id.length() + junk.length();
|
||||||
|
alloy_rlp::Header { list: true, payload_length }.encode(&mut buf);
|
||||||
|
|
||||||
|
// fields
|
||||||
|
fork_id.encode(&mut buf);
|
||||||
|
junk.encode(&mut buf);
|
||||||
|
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eip8_decode_enr_fork_id_entry() {
|
||||||
|
let enr_fork_id_entry_with_junk = junk_enr_fork_id_entry();
|
||||||
|
|
||||||
|
let mut buf = enr_fork_id_entry_with_junk.as_slice();
|
||||||
|
let decoded = EnrForkIdEntry::decode(&mut buf).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
decoded.fork_id,
|
||||||
|
ForkId { hash: ForkHash(hex!("deadbeef")), next: 0xBADDCAFE }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,12 @@ reth-network-types.workspace = true
|
|||||||
# ethereum
|
# ethereum
|
||||||
alloy-rlp = { workspace = true, features = ["derive"] }
|
alloy-rlp = { workspace = true, features = ["derive"] }
|
||||||
discv5.workspace = true
|
discv5.workspace = true
|
||||||
secp256k1 = { workspace = true, features = ["global-context", "rand-std", "recovery", "serde"] }
|
secp256k1 = { workspace = true, features = [
|
||||||
|
"global-context",
|
||||||
|
"rand-std",
|
||||||
|
"recovery",
|
||||||
|
"serde",
|
||||||
|
] }
|
||||||
enr.workspace = true
|
enr.workspace = true
|
||||||
# async/futures
|
# async/futures
|
||||||
tokio = { workspace = true, features = ["io-util", "net", "time"] }
|
tokio = { workspace = true, features = ["io-util", "net", "time"] }
|
||||||
@ -36,6 +41,7 @@ generic-array = "0.14"
|
|||||||
serde = { workspace = true, optional = true }
|
serde = { workspace = true, optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
assert_matches.workspace = true
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
tokio = { workspace = true, features = ["macros"] }
|
tokio = { workspace = true, features = ["macros"] }
|
||||||
reth-tracing.workspace = true
|
reth-tracing.workspace = true
|
||||||
|
|||||||
@ -215,7 +215,7 @@ impl NodeEndpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A [FindNode packet](https://github.com/ethereum/devp2p/blob/master/discv4.md#findnode-packet-0x03).
|
/// A [FindNode packet](https://github.com/ethereum/devp2p/blob/master/discv4.md#findnode-packet-0x03).
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, RlpEncodable, RlpDecodable)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, RlpEncodable)]
|
||||||
pub struct FindNode {
|
pub struct FindNode {
|
||||||
/// The target node's ID, a 64-byte secp256k1 public key.
|
/// The target node's ID, a 64-byte secp256k1 public key.
|
||||||
pub id: PeerId,
|
pub id: PeerId,
|
||||||
@ -223,8 +223,41 @@ pub struct FindNode {
|
|||||||
pub expire: u64,
|
pub expire: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Decodable for FindNode {
|
||||||
|
// NOTE(onbjerg): Manual implementation to satisfy EIP-8.
|
||||||
|
//
|
||||||
|
// See https://eips.ethereum.org/EIPS/eip-8
|
||||||
|
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
|
||||||
|
let b = &mut &**buf;
|
||||||
|
let rlp_head = Header::decode(b)?;
|
||||||
|
if !rlp_head.list {
|
||||||
|
return Err(RlpError::UnexpectedString)
|
||||||
|
}
|
||||||
|
let started_len = b.len();
|
||||||
|
|
||||||
|
let this = Self { id: Decodable::decode(b)?, expire: Decodable::decode(b)? };
|
||||||
|
|
||||||
|
// NOTE(onbjerg): Because of EIP-8, we only check that we did not consume *more* than the
|
||||||
|
// payload length, i.e. it is ok if payload length is greater than what we consumed, as we
|
||||||
|
// just discard the remaining list items
|
||||||
|
let consumed = started_len - b.len();
|
||||||
|
if consumed > rlp_head.payload_length {
|
||||||
|
return Err(RlpError::ListLengthMismatch {
|
||||||
|
expected: rlp_head.payload_length,
|
||||||
|
got: consumed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let rem = rlp_head.payload_length - consumed;
|
||||||
|
b.advance(rem);
|
||||||
|
*buf = *b;
|
||||||
|
|
||||||
|
Ok(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A [Neighbours packet](https://github.com/ethereum/devp2p/blob/master/discv4.md#neighbors-packet-0x04).
|
/// A [Neighbours packet](https://github.com/ethereum/devp2p/blob/master/discv4.md#neighbors-packet-0x04).
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, RlpEncodable, RlpDecodable)]
|
#[derive(Clone, Debug, Eq, PartialEq, RlpEncodable)]
|
||||||
pub struct Neighbours {
|
pub struct Neighbours {
|
||||||
/// The list of nodes containing IP, UDP port, TCP port, and node ID.
|
/// The list of nodes containing IP, UDP port, TCP port, and node ID.
|
||||||
pub nodes: Vec<NodeRecord>,
|
pub nodes: Vec<NodeRecord>,
|
||||||
@ -232,16 +265,82 @@ pub struct Neighbours {
|
|||||||
pub expire: u64,
|
pub expire: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Decodable for Neighbours {
|
||||||
|
// NOTE(onbjerg): Manual implementation to satisfy EIP-8.
|
||||||
|
//
|
||||||
|
// See https://eips.ethereum.org/EIPS/eip-8
|
||||||
|
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
|
||||||
|
let b = &mut &**buf;
|
||||||
|
let rlp_head = Header::decode(b)?;
|
||||||
|
if !rlp_head.list {
|
||||||
|
return Err(RlpError::UnexpectedString)
|
||||||
|
}
|
||||||
|
let started_len = b.len();
|
||||||
|
|
||||||
|
let this = Self { nodes: Decodable::decode(b)?, expire: Decodable::decode(b)? };
|
||||||
|
|
||||||
|
// NOTE(onbjerg): Because of EIP-8, we only check that we did not consume *more* than the
|
||||||
|
// payload length, i.e. it is ok if payload length is greater than what we consumed, as we
|
||||||
|
// just discard the remaining list items
|
||||||
|
let consumed = started_len - b.len();
|
||||||
|
if consumed > rlp_head.payload_length {
|
||||||
|
return Err(RlpError::ListLengthMismatch {
|
||||||
|
expected: rlp_head.payload_length,
|
||||||
|
got: consumed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let rem = rlp_head.payload_length - consumed;
|
||||||
|
b.advance(rem);
|
||||||
|
*buf = *b;
|
||||||
|
|
||||||
|
Ok(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A [ENRRequest packet](https://github.com/ethereum/devp2p/blob/master/discv4.md#enrrequest-packet-0x05).
|
/// A [ENRRequest packet](https://github.com/ethereum/devp2p/blob/master/discv4.md#enrrequest-packet-0x05).
|
||||||
///
|
///
|
||||||
/// This packet is used to request the current version of a node's Ethereum Node Record (ENR).
|
/// This packet is used to request the current version of a node's Ethereum Node Record (ENR).
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, RlpEncodable, RlpDecodable)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, RlpEncodable)]
|
||||||
pub struct EnrRequest {
|
pub struct EnrRequest {
|
||||||
/// The expiration timestamp for the request. No reply should be sent if it refers to a time in
|
/// The expiration timestamp for the request. No reply should be sent if it refers to a time in
|
||||||
/// the past.
|
/// the past.
|
||||||
pub expire: u64,
|
pub expire: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Decodable for EnrRequest {
|
||||||
|
// NOTE(onbjerg): Manual implementation to satisfy EIP-8.
|
||||||
|
//
|
||||||
|
// See https://eips.ethereum.org/EIPS/eip-8
|
||||||
|
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
|
||||||
|
let b = &mut &**buf;
|
||||||
|
let rlp_head = Header::decode(b)?;
|
||||||
|
if !rlp_head.list {
|
||||||
|
return Err(RlpError::UnexpectedString)
|
||||||
|
}
|
||||||
|
let started_len = b.len();
|
||||||
|
|
||||||
|
let this = Self { expire: Decodable::decode(b)? };
|
||||||
|
|
||||||
|
// NOTE(onbjerg): Because of EIP-8, we only check that we did not consume *more* than the
|
||||||
|
// payload length, i.e. it is ok if payload length is greater than what we consumed, as we
|
||||||
|
// just discard the remaining list items
|
||||||
|
let consumed = started_len - b.len();
|
||||||
|
if consumed > rlp_head.payload_length {
|
||||||
|
return Err(RlpError::ListLengthMismatch {
|
||||||
|
expected: rlp_head.payload_length,
|
||||||
|
got: consumed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let rem = rlp_head.payload_length - consumed;
|
||||||
|
b.advance(rem);
|
||||||
|
*buf = *b;
|
||||||
|
|
||||||
|
Ok(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A [ENRResponse packet](https://github.com/ethereum/devp2p/blob/master/discv4.md#enrresponse-packet-0x06).
|
/// A [ENRResponse packet](https://github.com/ethereum/devp2p/blob/master/discv4.md#enrresponse-packet-0x06).
|
||||||
///
|
///
|
||||||
/// This packet is used to respond to an ENRRequest packet and includes the requested ENR along with
|
/// This packet is used to respond to an ENRRequest packet and includes the requested ENR along with
|
||||||
@ -442,6 +541,7 @@ mod tests {
|
|||||||
test_utils::{rng_endpoint, rng_ipv4_record, rng_ipv6_record, rng_message},
|
test_utils::{rng_endpoint, rng_ipv4_record, rng_ipv6_record, rng_message},
|
||||||
DEFAULT_DISCOVERY_PORT, SAFE_MAX_DATAGRAM_NEIGHBOUR_RECORDS,
|
DEFAULT_DISCOVERY_PORT, SAFE_MAX_DATAGRAM_NEIGHBOUR_RECORDS,
|
||||||
};
|
};
|
||||||
|
use assert_matches::assert_matches;
|
||||||
use enr::EnrPublicKey;
|
use enr::EnrPublicKey;
|
||||||
use rand::{thread_rng, Rng, RngCore};
|
use rand::{thread_rng, Rng, RngCore};
|
||||||
use reth_primitives::{hex, ForkHash};
|
use reth_primitives::{hex, ForkHash};
|
||||||
@ -769,4 +869,97 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert!(decoded_enr.verify());
|
assert!(decoded_enr.verify());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod eip8 {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn junk_enr_request() -> Vec<u8> {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
// enr request is just an expiration
|
||||||
|
let expire: u64 = 123456;
|
||||||
|
|
||||||
|
// add some junk
|
||||||
|
let junk: u64 = 112233;
|
||||||
|
|
||||||
|
// rlp header encoding
|
||||||
|
let payload_length = expire.length() + junk.length();
|
||||||
|
alloy_rlp::Header { list: true, payload_length }.encode(&mut buf);
|
||||||
|
|
||||||
|
// fields
|
||||||
|
expire.encode(&mut buf);
|
||||||
|
junk.encode(&mut buf);
|
||||||
|
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks that junk data at the end of the packet is discarded according to eip-8
|
||||||
|
#[test]
|
||||||
|
fn eip8_decode_enr_request() {
|
||||||
|
let enr_request_with_junk = junk_enr_request();
|
||||||
|
|
||||||
|
let mut buf = enr_request_with_junk.as_slice();
|
||||||
|
let decoded = EnrRequest::decode(&mut buf).unwrap();
|
||||||
|
assert_eq!(decoded.expire, 123456);
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks that junk data at the end of the packet is discarded according to eip-8
|
||||||
|
//
|
||||||
|
// test vector from eip-8: https://eips.ethereum.org/EIPS/eip-8
|
||||||
|
#[test]
|
||||||
|
fn eip8_decode_findnode() {
|
||||||
|
let findnode_with_junk = hex!("c7c44041b9f7c7e41934417ebac9a8e1a4c6298f74553f2fcfdcae6ed6fe53163eb3d2b52e39fe91831b8a927bf4fc222c3902202027e5e9eb812195f95d20061ef5cd31d502e47ecb61183f74a504fe04c51e73df81f25c4d506b26db4517490103f84eb840ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f8443b9a35582999983999999280dc62cc8255c73471e0a61da0c89acdc0e035e260add7fc0c04ad9ebf3919644c91cb247affc82b69bd2ca235c71eab8e49737c937a2c396");
|
||||||
|
|
||||||
|
let buf = findnode_with_junk.as_slice();
|
||||||
|
let decoded = Message::decode(buf).unwrap();
|
||||||
|
|
||||||
|
let expected_id = hex!("ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f");
|
||||||
|
assert_matches!(decoded.msg, Message::FindNode(FindNode { id, expire: 1136239445 }) if id == expected_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks that junk data at the end of the packet is discarded according to eip-8
|
||||||
|
//
|
||||||
|
// test vector from eip-8: https://eips.ethereum.org/EIPS/eip-8
|
||||||
|
#[test]
|
||||||
|
fn eip8_decode_neighbours() {
|
||||||
|
let neighbours_with_junk = hex!("c679fc8fe0b8b12f06577f2e802d34f6fa257e6137a995f6f4cbfc9ee50ed3710faf6e66f932c4c8d81d64343f429651328758b47d3dbc02c4042f0fff6946a50f4a49037a72bb550f3a7872363a83e1b9ee6469856c24eb4ef80b7535bcf99c0004f9015bf90150f84d846321163782115c82115db8403155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32f84984010203040101b840312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069dbf8599020010db83c4d001500000000abcdef12820d05820d05b84038643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aacf8599020010db885a308d313198a2e037073488203e78203e8b8408dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df738443b9a355010203b525a138aa34383fec3d2719a0");
|
||||||
|
|
||||||
|
let buf = neighbours_with_junk.as_slice();
|
||||||
|
let decoded = Message::decode(buf).unwrap();
|
||||||
|
|
||||||
|
let _ = NodeRecord {
|
||||||
|
address: "99.33.22.55".parse().unwrap(),
|
||||||
|
tcp_port: 4444,
|
||||||
|
udp_port: 4445,
|
||||||
|
id: hex!("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32").into(),
|
||||||
|
}.length();
|
||||||
|
|
||||||
|
let expected_nodes: Vec<NodeRecord> = vec![
|
||||||
|
NodeRecord {
|
||||||
|
address: "99.33.22.55".parse().unwrap(),
|
||||||
|
tcp_port: 4444,
|
||||||
|
udp_port: 4445,
|
||||||
|
id: hex!("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32").into(),
|
||||||
|
},
|
||||||
|
NodeRecord {
|
||||||
|
address: "1.2.3.4".parse().unwrap(),
|
||||||
|
tcp_port: 1,
|
||||||
|
udp_port: 1,
|
||||||
|
id: hex!("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db").into(),
|
||||||
|
},
|
||||||
|
NodeRecord {
|
||||||
|
address: "2001:db8:3c4d:15::abcd:ef12".parse().unwrap(),
|
||||||
|
tcp_port: 3333,
|
||||||
|
udp_port: 3333,
|
||||||
|
id: hex!("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac").into(),
|
||||||
|
},
|
||||||
|
NodeRecord {
|
||||||
|
address: "2001:db8:85a3:8d3:1319:8a2e:370:7348".parse().unwrap(),
|
||||||
|
tcp_port: 999,
|
||||||
|
udp_port: 1000,
|
||||||
|
id: hex!("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73").into(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
assert_matches!(decoded.msg, Message::Neighbours(Neighbours { nodes, expire: 1136239445 }) if nodes == expected_nodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user