diff --git a/lean_client/Cargo.lock b/lean_client/Cargo.lock index 93fb9dd..5b622b9 100644 --- a/lean_client/Cargo.lock +++ b/lean_client/Cargo.lock @@ -79,7 +79,7 @@ dependencies = [ "hashbrown 0.16.0", "indexmap 2.11.4", "itoa", - "k256 0.13.4", + "k256", "keccak-asm", "paste", "proptest", @@ -500,7 +500,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" dependencies = [ - "base64 0.22.1", + "base64", "http", "log", "url", @@ -529,12 +529,6 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" -[[package]] -name = "base16ct" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" - [[package]] name = "base16ct" version = "0.2.0" @@ -551,12 +545,6 @@ dependencies = [ "match-lookup", ] -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - [[package]] name = "base64" version = "0.22.1" @@ -874,9 +862,9 @@ dependencies = [ [[package]] name = "convert_case" -version = "0.7.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" dependencies = [ "unicode-segmentation", ] @@ -961,18 +949,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" -[[package]] -name = "crypto-bigint" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "subtle", - "zeroize", -] - [[package]] name = "crypto-bigint" version = "0.5.5" @@ -1108,13 +1084,14 @@ dependencies = [ ] [[package]] -name = "der" -version = "0.6.1" +name = "delay_map" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +checksum = "88e365f083a5cb5972d50ce8b1b2c9f125dc5ec0f50c0248cfb568ae59efcf0b" dependencies = [ - "const-oid", - "zeroize", + "futures", + "tokio", + "tokio-util", ] [[package]] @@ -1164,22 +1141,23 @@ dependencies = [ [[package]] name = "derive_more" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ "convert_case", "proc-macro2", "quote", + "rustc_version 0.4.1", "syn 2.0.106", "unicode-xid", ] @@ -1211,6 +1189,37 @@ dependencies = [ "subtle", ] +[[package]] +name = "discv5" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f170f4f6ed0e1df52bf43b403899f0081917ecf1500bfe312505cc3b515a8899" +dependencies = [ + "aes", + "aes-gcm", + "alloy-rlp", + "arrayvec", + "ctr", + "delay_map", + "enr", + "fnv", + "futures", + "hashlink", + "hex", + "hkdf", + "lazy_static", + "lru", + "more-asserts", + "parking_lot", + "rand 0.8.5", + "smallvec", + "socket2 0.5.10", + "tokio", + "tracing", + "uint 0.10.0", + "zeroize", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -1240,30 +1249,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc5d6d6a8504f8caedd7de14576464383900cd3840b7033a7a3dce5ac00121ca" -[[package]] -name = "ecdsa" -version = "0.14.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" -dependencies = [ - "der 0.6.1", - "elliptic-curve 0.12.3", - "rfc6979 0.3.1", - "signature 1.6.4", -] - [[package]] name = "ecdsa" version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "der 0.7.10", + "der", "digest 0.10.7", - "elliptic-curve 0.13.8", - "rfc6979 0.4.0", - "signature 2.2.0", - "spki 0.7.3", + "elliptic-curve", + "rfc6979", + "signature", + "spki", ] [[package]] @@ -1272,8 +1269,8 @@ version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ - "pkcs8 0.10.2", - "signature 2.2.0", + "pkcs8", + "signature", ] [[package]] @@ -1309,59 +1306,39 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -[[package]] -name = "elliptic-curve" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" -dependencies = [ - "base16ct 0.1.1", - "crypto-bigint 0.4.9", - "der 0.6.1", - "digest 0.10.7", - "ff 0.12.1", - "generic-array", - "group 0.12.1", - "pkcs8 0.9.0", - "rand_core 0.6.4", - "sec1 0.3.0", - "subtle", - "zeroize", -] - [[package]] name = "elliptic-curve" version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ - "base16ct 0.2.0", - "crypto-bigint 0.5.5", + "base16ct", + "crypto-bigint", "digest 0.10.7", - "ff 0.13.1", + "ff", "generic-array", - "group 0.13.0", - "pkcs8 0.10.2", + "group", + "pkcs8", "rand_core 0.6.4", - "sec1 0.7.3", + "sec1", "subtle", "zeroize", ] [[package]] name = "enr" -version = "0.7.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "492a7e5fc2504d5fdce8e124d3e263b244a68b283cac67a69eda0cd43e0aebad" +checksum = "851bd664a3d3a3c175cff92b2f0df02df3c541b4895d0ae307611827aae46152" dependencies = [ - "base64 0.13.1", - "bs58 0.4.0", + "alloy-rlp", + "base64", "bytes", + "ed25519-dalek", "hex", - "k256 0.11.6", + "k256", "log", "rand 0.8.5", - "rlp", "serde", "sha3", "zeroize", @@ -1412,7 +1389,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.1", ] [[package]] @@ -1439,7 +1416,7 @@ dependencies = [ "impl-rlp", "impl-serde", "primitive-types", - "uint", + "uint 0.9.5", ] [[package]] @@ -1519,16 +1496,6 @@ dependencies = [ "bytes", ] -[[package]] -name = "ff" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "ff" version = "0.13.1" @@ -1791,24 +1758,13 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" -[[package]] -name = "group" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" -dependencies = [ - "ff 0.12.1", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "group" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "ff 0.13.1", + "ff", "rand_core 0.6.4", "subtle", ] @@ -2311,7 +2267,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", - "hashbrown 0.15.5", + "hashbrown 0.16.0", "serde", "serde_core", ] @@ -2401,18 +2357,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "k256" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" -dependencies = [ - "cfg-if", - "ecdsa 0.14.8", - "elliptic-curve 0.12.3", - "sha2 0.10.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "k256" version = "0.13.4" @@ -2420,11 +2364,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", - "ecdsa 0.16.9", - "elliptic-curve 0.13.8", + "ecdsa", + "elliptic-curve", "once_cell", "sha2 0.10.9 (registry+https://github.com/rust-lang/crates.io-index)", - "signature 2.2.0", + "signature", ] [[package]] @@ -2633,7 +2577,7 @@ checksum = "c7f58e37d8d6848e5c4c9e3c35c6f61133235bff2960c9c00a663b0849301221" dependencies = [ "async-channel", "asynchronous-codec 0.7.0", - "base64 0.22.1", + "base64", "byteorder", "bytes", "either", @@ -2704,7 +2648,7 @@ dependencies = [ "bs58 0.5.1", "ed25519-dalek", "hkdf", - "k256 0.13.4", + "k256", "multihash 0.19.3", "quick-protobuf", "rand 0.8.5", @@ -3031,6 +2975,12 @@ dependencies = [ "uuid", ] +[[package]] +name = "more-asserts" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fafa6961cabd9c63bcd77a45d7e3b7f3b552b70417831fb0f56db717e72407e" + [[package]] name = "multiaddr" version = "0.17.1" @@ -3216,17 +3166,21 @@ dependencies = [ "anyhow", "async-trait", "containers", - "enr", + "derive_more", + "discv5", "futures", + "hex", "libp2p", "libp2p-identity 0.2.12", "libp2p-mplex", "parking_lot", "rand 0.8.5", "serde", + "serde_yaml", "sha2 0.10.9 (registry+https://github.com/rust-lang/crates.io-index)", "snap", "ssz", + "tiny-keccak", "tokio", "tracing", "yamux 0.12.1", @@ -3265,7 +3219,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.1", ] [[package]] @@ -3552,7 +3506,7 @@ version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ - "base64 0.22.1", + "base64", "serde_core", ] @@ -3604,24 +3558,14 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkcs8" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" -dependencies = [ - "der 0.6.1", - "spki 0.6.0", -] - [[package]] name = "pkcs8" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der 0.7.10", - "spki 0.7.3", + "der", + "spki", ] [[package]] @@ -3711,7 +3655,7 @@ dependencies = [ "impl-codec", "impl-rlp", "impl-serde", - "uint", + "uint 0.9.5", ] [[package]] @@ -4093,17 +4037,6 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b3789b30bd25ba102de4beabd95d21ac45b69b1be7d14522bab988c526d6799" -[[package]] -name = "rfc6979" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" -dependencies = [ - "crypto-bigint 0.4.9", - "hmac", - "zeroize", -] - [[package]] name = "rfc6979" version = "0.4.0" @@ -4268,7 +4201,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.1", ] [[package]] @@ -4382,30 +4315,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sec1" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" -dependencies = [ - "base16ct 0.1.1", - "der 0.6.1", - "generic-array", - "pkcs8 0.9.0", - "subtle", - "zeroize", -] - [[package]] name = "sec1" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ - "base16ct 0.2.0", - "der 0.7.10", + "base16ct", + "der", "generic-array", - "pkcs8 0.10.2", + "pkcs8", "subtle", "zeroize", ] @@ -4500,7 +4419,7 @@ version = "3.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c522100790450cf78eeac1507263d0a350d4d5b30df0c8e1fe051a10c22b376e" dependencies = [ - "base64 0.22.1", + "base64", "chrono", "hex", "indexmap 1.9.3", @@ -4617,16 +4536,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "1.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" -dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", -] - [[package]] name = "signature" version = "2.2.0" @@ -4701,16 +4610,6 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spki" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" -dependencies = [ - "base64ct", - "der 0.6.1", -] - [[package]] name = "spki" version = "0.7.3" @@ -4718,7 +4617,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", - "der 0.7.10", + "der", ] [[package]] @@ -4919,7 +4818,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.1", ] [[package]] @@ -5074,6 +4973,7 @@ dependencies = [ "futures-core", "futures-sink", "pin-project-lite", + "slab", "tokio", ] @@ -5128,6 +5028,7 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -5238,6 +5139,18 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "uint" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unarray" version = "0.1.4" diff --git a/lean_client/networking/Cargo.toml b/lean_client/networking/Cargo.toml index f107994..c06524e 100644 --- a/lean_client/networking/Cargo.toml +++ b/lean_client/networking/Cargo.toml @@ -11,7 +11,6 @@ snap = {workspace = true} sha2 = { workspace = true } anyhow = { workspace = true } async-trait = "0.1" -enr = "0.7" futures = "0.3" libp2p-identity = { version = "0.2", features = ["secp256k1"] } libp2p-mplex = "0.39" @@ -22,3 +21,8 @@ tracing = "0.1" yamux = "0.12" ssz = { workspace = true } serde = { workspace = true } +serde_yaml = { workspace = true } +discv5 = "0.10.2" +hex = "0.4.3" +tiny-keccak = "2.0.2" +derive_more = "2.1.1" diff --git a/lean_client/networking/src/enr_ext.rs b/lean_client/networking/src/enr_ext.rs new file mode 100644 index 0000000..d7511e4 --- /dev/null +++ b/lean_client/networking/src/enr_ext.rs @@ -0,0 +1,400 @@ +// This file is copied from https://github.com/grandinetech/eth2_libp2p, which itself was ported from lighthouse. +// Licensed under Apache License 2.0. Full license text is here https://github.com/grandinetech/eth2_libp2p/blob/main/LICENSE + +//! ENR extension trait to support libp2p integration. + +use anyhow::{Result, bail}; +use discv5::{ + Enr, + enr::{CombinedKey, CombinedPublicKey}, +}; +use libp2p::core::multiaddr::Protocol; +use libp2p::identity::{KeyType, Keypair, PublicKey, ed25519, secp256k1}; +use libp2p::{Multiaddr, PeerId}; +use tiny_keccak::{Hasher, Keccak}; + +pub const QUIC_ENR_KEY: &str = "quic"; +pub const QUIC6_ENR_KEY: &str = "quic6"; + +/// Extend ENR for libp2p types. +pub trait EnrExt { + /// The libp2p `PeerId` for the record. + fn peer_id(&self) -> PeerId; + + /// Returns a list of multiaddrs if the ENR has an `ip` and one of [`tcp`,`udp`,`quic`] key **or** an `ip6` and one of [`tcp6`,`udp6`,`quic6`]. + /// The vector remains empty if these fields are not defined. + fn multiaddr(&self) -> Vec; + + /// Returns a list of multiaddrs with the `PeerId` prepended. + fn multiaddr_p2p(&self) -> Vec; + + /// Returns any multiaddrs that contain the TCP protocol with the `PeerId` prepended. + fn multiaddr_p2p_tcp(&self) -> Vec; + + /// Returns any multiaddrs that contain the UDP protocol with the `PeerId` prepended. + fn multiaddr_p2p_udp(&self) -> Vec; + + /// Returns any multiaddrs that contain the TCP protocol. + fn multiaddr_tcp(&self) -> Vec; + + /// Returns any QUIC multiaddrs that are registered in this ENR. + fn multiaddr_quic(&self) -> Vec; + + /// Returns the quic port if one is set. + fn quic4(&self) -> Option; + + /// Returns the quic6 port if one is set. + fn quic6(&self) -> Option; +} + +/// Extend ENR CombinedPublicKey for libp2p types. +pub trait CombinedKeyPublicExt { + /// Converts the publickey into a peer id, without consuming the key. + fn as_peer_id(&self) -> PeerId; +} + +/// Extend ENR CombinedKey for conversion to libp2p keys. +pub trait CombinedKeyExt { + /// Converts a libp2p key into an ENR combined key. + fn from_libp2p(key: Keypair) -> Result; + + /// Converts a [`secp256k1::Keypair`] into and Enr [`CombinedKey`]. + fn from_secp256k1(key: &secp256k1::Keypair) -> CombinedKey; +} + +impl EnrExt for Enr { + /// The libp2p `PeerId` for the record. + fn peer_id(&self) -> PeerId { + self.public_key().as_peer_id() + } + + /// Returns the quic port if one is set. + fn quic4(&self) -> Option { + self.get_decodable(QUIC_ENR_KEY).and_then(Result::ok) + } + + /// Returns the quic6 port if one is set. + fn quic6(&self) -> Option { + self.get_decodable(QUIC6_ENR_KEY).and_then(Result::ok) + } + + /// Returns a list of multiaddrs if the ENR has an `ip` and either a `tcp`, `quic` or `udp` key **or** an `ip6` and either a `tcp6` `quic6` or `udp6`. + /// The vector remains empty if these fields are not defined. + fn multiaddr(&self) -> Vec { + let mut multiaddrs: Vec = Vec::new(); + if let Some(ip) = self.ip4() { + if let Some(udp) = self.udp4() { + let mut multiaddr: Multiaddr = ip.into(); + multiaddr.push(Protocol::Udp(udp)); + multiaddrs.push(multiaddr); + } + if let Some(quic) = self.quic4() { + let mut multiaddr: Multiaddr = ip.into(); + multiaddr.push(Protocol::Udp(quic)); + multiaddr.push(Protocol::QuicV1); + multiaddrs.push(multiaddr); + } + + if let Some(tcp) = self.tcp4() { + let mut multiaddr: Multiaddr = ip.into(); + multiaddr.push(Protocol::Tcp(tcp)); + multiaddrs.push(multiaddr); + } + } + if let Some(ip6) = self.ip6() { + if let Some(udp6) = self.udp6() { + let mut multiaddr: Multiaddr = ip6.into(); + multiaddr.push(Protocol::Udp(udp6)); + multiaddrs.push(multiaddr); + } + + if let Some(quic6) = self.quic6() { + let mut multiaddr: Multiaddr = ip6.into(); + multiaddr.push(Protocol::Udp(quic6)); + multiaddr.push(Protocol::QuicV1); + multiaddrs.push(multiaddr); + } + + if let Some(tcp6) = self.tcp6() { + let mut multiaddr: Multiaddr = ip6.into(); + multiaddr.push(Protocol::Tcp(tcp6)); + multiaddrs.push(multiaddr); + } + } + multiaddrs + } + + /// Returns a list of multiaddrs if the ENR has an `ip` and either a `tcp` or `udp` key **or** an `ip6` and either a `tcp6` or `udp6`. + /// The vector remains empty if these fields are not defined. + /// + /// This also prepends the `PeerId` into each multiaddr with the `P2p` protocol. + fn multiaddr_p2p(&self) -> Vec { + let peer_id = self.peer_id(); + let mut multiaddrs: Vec = Vec::new(); + if let Some(ip) = self.ip4() { + if let Some(udp) = self.udp4() { + let mut multiaddr: Multiaddr = ip.into(); + multiaddr.push(Protocol::Udp(udp)); + multiaddr.push(Protocol::P2p(peer_id)); + multiaddrs.push(multiaddr); + } + + if let Some(tcp) = self.tcp4() { + let mut multiaddr: Multiaddr = ip.into(); + multiaddr.push(Protocol::Tcp(tcp)); + multiaddr.push(Protocol::P2p(peer_id)); + multiaddrs.push(multiaddr); + } + } + if let Some(ip6) = self.ip6() { + if let Some(udp6) = self.udp6() { + let mut multiaddr: Multiaddr = ip6.into(); + multiaddr.push(Protocol::Udp(udp6)); + multiaddr.push(Protocol::P2p(peer_id)); + multiaddrs.push(multiaddr); + } + + if let Some(tcp6) = self.tcp6() { + let mut multiaddr: Multiaddr = ip6.into(); + multiaddr.push(Protocol::Tcp(tcp6)); + multiaddr.push(Protocol::P2p(peer_id)); + multiaddrs.push(multiaddr); + } + } + multiaddrs + } + + /// Returns a list of multiaddrs if the ENR has an `ip` and a `tcp` key **or** an `ip6` and a `tcp6`. + /// The vector remains empty if these fields are not defined. + /// + /// This also prepends the `PeerId` into each multiaddr with the `P2p` protocol. + fn multiaddr_p2p_tcp(&self) -> Vec { + let peer_id = self.peer_id(); + let mut multiaddrs: Vec = Vec::new(); + if let Some(ip) = self.ip4() { + if let Some(tcp) = self.tcp4() { + let mut multiaddr: Multiaddr = ip.into(); + multiaddr.push(Protocol::Tcp(tcp)); + multiaddr.push(Protocol::P2p(peer_id)); + multiaddrs.push(multiaddr); + } + } + if let Some(ip6) = self.ip6() { + if let Some(tcp6) = self.tcp6() { + let mut multiaddr: Multiaddr = ip6.into(); + multiaddr.push(Protocol::Tcp(tcp6)); + multiaddr.push(Protocol::P2p(peer_id)); + multiaddrs.push(multiaddr); + } + } + multiaddrs + } + + /// Returns a list of multiaddrs if the ENR has an `ip` and a `udp` key **or** an `ip6` and a `udp6`. + /// The vector remains empty if these fields are not defined. + /// + /// This also prepends the `PeerId` into each multiaddr with the `P2p` protocol. + fn multiaddr_p2p_udp(&self) -> Vec { + let peer_id = self.peer_id(); + let mut multiaddrs: Vec = Vec::new(); + if let Some(ip) = self.ip4() { + if let Some(udp) = self.udp4() { + let mut multiaddr: Multiaddr = ip.into(); + multiaddr.push(Protocol::Udp(udp)); + multiaddr.push(Protocol::P2p(peer_id)); + multiaddrs.push(multiaddr); + } + } + if let Some(ip6) = self.ip6() { + if let Some(udp6) = self.udp6() { + let mut multiaddr: Multiaddr = ip6.into(); + multiaddr.push(Protocol::Udp(udp6)); + multiaddr.push(Protocol::P2p(peer_id)); + multiaddrs.push(multiaddr); + } + } + multiaddrs + } + + /// Returns a list of multiaddrs if the ENR has an `ip` and a `quic` key **or** an `ip6` and a `quic6`. + fn multiaddr_quic(&self) -> Vec { + let mut multiaddrs: Vec = Vec::new(); + if let Some(quic_port) = self.quic4() { + if let Some(ip) = self.ip4() { + let mut multiaddr: Multiaddr = ip.into(); + multiaddr.push(Protocol::Udp(quic_port)); + multiaddr.push(Protocol::QuicV1); + multiaddrs.push(multiaddr); + } + } + + if let Some(quic6_port) = self.quic6() { + if let Some(ip6) = self.ip6() { + let mut multiaddr: Multiaddr = ip6.into(); + multiaddr.push(Protocol::Udp(quic6_port)); + multiaddr.push(Protocol::QuicV1); + multiaddrs.push(multiaddr); + } + } + multiaddrs + } + + /// Returns a list of multiaddrs if the ENR has an `ip` and either a `tcp` or `udp` key **or** an `ip6` and either a `tcp6` or `udp6`. + fn multiaddr_tcp(&self) -> Vec { + let mut multiaddrs: Vec = Vec::new(); + if let Some(ip) = self.ip4() { + if let Some(tcp) = self.tcp4() { + let mut multiaddr: Multiaddr = ip.into(); + multiaddr.push(Protocol::Tcp(tcp)); + multiaddrs.push(multiaddr); + } + } + if let Some(ip6) = self.ip6() { + if let Some(tcp6) = self.tcp6() { + let mut multiaddr: Multiaddr = ip6.into(); + multiaddr.push(Protocol::Tcp(tcp6)); + multiaddrs.push(multiaddr); + } + } + multiaddrs + } +} + +impl CombinedKeyPublicExt for CombinedPublicKey { + /// Converts the publickey into a peer id, without consuming the key. + /// + /// This is only available with the `libp2p` feature flag. + fn as_peer_id(&self) -> PeerId { + match self { + Self::Secp256k1(pk) => { + let pk_bytes = pk.to_sec1_bytes(); + let libp2p_pk: PublicKey = secp256k1::PublicKey::try_from_bytes(&pk_bytes) + .expect("valid public key") + .into(); + PeerId::from_public_key(&libp2p_pk) + } + Self::Ed25519(pk) => { + let pk_bytes = pk.to_bytes(); + let libp2p_pk: PublicKey = ed25519::PublicKey::try_from_bytes(&pk_bytes) + .expect("valid public key") + .into(); + PeerId::from_public_key(&libp2p_pk) + } + } + } +} + +impl CombinedKeyExt for CombinedKey { + fn from_libp2p(key: Keypair) -> Result { + match key.key_type() { + KeyType::Secp256k1 => { + let key = key.try_into_secp256k1().expect("right key type"); + let secret = + discv5::enr::k256::ecdsa::SigningKey::from_slice(&key.secret().to_bytes()) + .expect("libp2p key must be valid"); + Ok(CombinedKey::Secp256k1(secret)) + } + KeyType::Ed25519 => { + let key = key.try_into_ed25519().expect("right key type"); + let ed_keypair = discv5::enr::ed25519_dalek::SigningKey::from_bytes( + &(key.to_bytes()[..32]) + .try_into() + .expect("libp2p key must be valid"), + ); + Ok(CombinedKey::from(ed_keypair)) + } + _ => bail!("Unsupported keypair kind"), + } + } + fn from_secp256k1(key: &secp256k1::Keypair) -> Self { + let secret = discv5::enr::k256::ecdsa::SigningKey::from_slice(&key.secret().to_bytes()) + .expect("libp2p key must be valid"); + CombinedKey::Secp256k1(secret) + } +} + +// helper function to convert a peer_id to a node_id. This is only possible for secp256k1/ed25519 libp2p +// peer_ids +pub fn peer_id_to_node_id(peer_id: &PeerId) -> Result { + // A libp2p peer id byte representation should be 2 length bytes + 4 protobuf bytes + compressed pk bytes + // if generated from a PublicKey with Identity multihash. + let pk_bytes = &peer_id.to_bytes()[2..]; + + let public_key = PublicKey::try_decode_protobuf(pk_bytes).map_err(|e| { + format!( + " Cannot parse libp2p public key public key from peer id: {}", + e + ) + })?; + + match public_key.key_type() { + KeyType::Secp256k1 => { + let pk = public_key + .clone() + .try_into_secp256k1() + .expect("right key type"); + let uncompressed_key_bytes = &pk.to_bytes_uncompressed()[1..]; + let mut output = [0_u8; 32]; + let mut hasher = Keccak::v256(); + hasher.update(uncompressed_key_bytes); + hasher.finalize(&mut output); + Ok(discv5::enr::NodeId::parse(&output).expect("Must be correct length")) + } + KeyType::Ed25519 => { + let pk = public_key + .clone() + .try_into_ed25519() + .expect("right key type"); + let uncompressed_key_bytes = pk.to_bytes(); + let mut output = [0_u8; 32]; + let mut hasher = Keccak::v256(); + hasher.update(&uncompressed_key_bytes); + hasher.finalize(&mut output); + Ok(discv5::enr::NodeId::parse(&output).expect("Must be correct length")) + } + + _ => Err(format!("Unsupported public key from peer {}", peer_id)), + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_secp256k1_peer_id_conversion() { + let sk_hex = "df94a73d528434ce2309abb19c16aedb535322797dbd59c157b1e04095900f48"; + let sk_bytes = hex::decode(sk_hex).unwrap(); + let secret_key = discv5::enr::k256::ecdsa::SigningKey::from_slice(&sk_bytes).unwrap(); + + let libp2p_sk = secp256k1::SecretKey::try_from_bytes(sk_bytes).unwrap(); + let secp256k1_kp: secp256k1::Keypair = libp2p_sk.into(); + let libp2p_kp: Keypair = secp256k1_kp.into(); + let peer_id = libp2p_kp.public().to_peer_id(); + + let enr = discv5::enr::Enr::builder().build(&secret_key).unwrap(); + let node_id = peer_id_to_node_id(&peer_id).unwrap(); + + assert_eq!(enr.node_id(), node_id); + } + + #[test] + fn test_ed25519_peer_conversion() { + let sk_hex = "4dea8a5072119927e9d243a7d953f2f4bc95b70f110978e2f9bc7a9000e4b261"; + let sk_bytes = hex::decode(sk_hex).unwrap(); + let secret_key = discv5::enr::ed25519_dalek::SigningKey::from_bytes( + &sk_bytes.clone().try_into().unwrap(), + ); + + let libp2p_sk = ed25519::SecretKey::try_from_bytes(sk_bytes).unwrap(); + let secp256k1_kp: ed25519::Keypair = libp2p_sk.into(); + let libp2p_kp: Keypair = secp256k1_kp.into(); + let peer_id = libp2p_kp.public().to_peer_id(); + + let enr = discv5::enr::Enr::builder().build(&secret_key).unwrap(); + let node_id = peer_id_to_node_id(&peer_id).unwrap(); + + assert_eq!(enr.node_id(), node_id); + } +} diff --git a/lean_client/networking/src/lib.rs b/lean_client/networking/src/lib.rs index 2a54c28..16e0483 100644 --- a/lean_client/networking/src/lib.rs +++ b/lean_client/networking/src/lib.rs @@ -1,5 +1,6 @@ pub mod bootnodes; pub mod compressor; +mod enr_ext; pub mod gossipsub; pub mod network; pub mod req_resp; diff --git a/lean_client/networking/src/network/service.rs b/lean_client/networking/src/network/service.rs index 63b6f9c..b428cb5 100644 --- a/lean_client/networking/src/network/service.rs +++ b/lean_client/networking/src/network/service.rs @@ -1,5 +1,6 @@ use std::{ collections::HashMap, + fs::File, net::IpAddr, num::{NonZeroU8, NonZeroUsize}, sync::Arc, @@ -8,6 +9,8 @@ use std::{ use anyhow::{Result, anyhow}; use containers::ssz::SszWrite; +use derive_more::Display; +use discv5::Enr; use futures::StreamExt; use libp2p::{ Multiaddr, SwarmBuilder, @@ -19,6 +22,7 @@ use libp2p::{ }; use libp2p_identity::{Keypair, PeerId}; use parking_lot::Mutex; +use serde::{Deserialize, Serialize}; use tokio::select; use tokio::time::{Duration, MissedTickBehavior, interval}; use tracing::{debug, info, trace, warn}; @@ -26,6 +30,7 @@ use tracing::{debug, info, trace, warn}; use crate::{ bootnodes::{BootnodeSource, StaticBootnodes}, compressor::Compressor, + enr_ext::EnrExt, gossipsub::{self, config::GossipsubConfig, message::GossipsubMessage, topic::GossipsubKind}, network::behaviour::{LeanNetworkBehaviour, LeanNetworkBehaviourEvent}, req_resp::{self, BLOCKS_BY_ROOT_PROTOCOL_V1, LeanRequest, ReqRespMessage, STATUS_PROTOCOL_V1}, @@ -42,6 +47,55 @@ pub struct NetworkServiceConfig { bootnodes: StaticBootnodes, } +#[derive(Debug, Clone, Serialize, Deserialize, Display)] +#[serde(untagged)] +enum Bootnode { + Multiaddr(Multiaddr), + Enr(Enr), +} + +impl Bootnode { + fn addrs(&self) -> Vec { + match self { + Self::Multiaddr(addr) => vec![addr.clone()], + Self::Enr(enr) => enr.multiaddr_quic(), + } + } +} + +fn parse_bootnode_argument(arg: &str) -> Vec { + if let Some(value) = arg.parse::().ok() { + return vec![Bootnode::Multiaddr(value)]; + }; + + if let Some(rec) = arg.parse::().ok() { + return vec![Bootnode::Enr(rec)]; + } + + let Some(file) = File::open(&arg).ok() else { + warn!( + "value {arg:?} provided as bootnode is not recognized - it is not valid multiaddr nor valid path to file containing bootnodes." + ); + + return Vec::new(); + }; + + let bootnodes: Vec = match serde_yaml::from_reader(file) { + Ok(value) => value, + Err(err) => { + warn!("failed to read bootnodes from {arg:?}: {err:?}"); + + return Vec::new(); + } + }; + + if bootnodes.is_empty() { + warn!("provided file with bootnodes {arg:?} is empty"); + } + + bootnodes +} + impl NetworkServiceConfig { pub fn new( gossipsub_config: GossipsubConfig, @@ -52,7 +106,15 @@ impl NetworkServiceConfig { let bootnodes = StaticBootnodes::new( bootnodes .iter() - .filter_map(|addr_str| addr_str.parse().ok()) + .flat_map(|addr_str| parse_bootnode_argument(&addr_str)) + .flat_map(|bootnode| { + let addrs = bootnode.addrs(); + if addrs.is_empty() { + warn!("bootnode {bootnode} doesn't have valid address to dial"); + } + + addrs + }) .collect::>(), );