From 69bf93f3ea0a1cc0e9a6a6ba6d55c347dda0ea72 Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Mon, 1 Jul 2024 11:42:37 -0700 Subject: [PATCH] Enhanced IPv6 support (discovery) (#8410) --- .../v1/node/GetIdentityIntegrationTest.java | 2 +- .../pegasys/teku/api/NetworkDataProvider.java | 3 +- gradle/versions.gradle | 2 +- .../infrastructure/io/IPVersionResolver.java | 7 +- .../teku/networking/eth2/P2PConfig.java | 3 + .../p2p/discovery/DiscoveryConfig.java | 82 +++++++++--- .../p2p/discovery/DiscoveryNetwork.java | 5 +- .../p2p/discovery/DiscoveryService.java | 3 +- .../p2p/discovery/discv5/DiscV5Service.java | 124 ++++++++++++++---- .../discovery/discv5/NodeRecordConverter.java | 2 + .../discovery/noop/NoOpDiscoveryService.java | 3 +- .../networking/p2p/libp2p/LibP2PNetwork.java | 2 +- .../networking/p2p/mock/MockP2PNetwork.java | 2 +- .../p2p/network/DelegatingP2PNetwork.java | 4 +- .../networking/p2p/network/P2PNetwork.java | 2 +- .../p2p/network/config/NetworkConfig.java | 2 +- .../pegasys/teku/cli/options/P2POptions.java | 28 ++++ .../teku/cli/options/P2POptionsTest.java | 2 +- 18 files changed, 217 insertions(+), 61 deletions(-) diff --git a/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v1/node/GetIdentityIntegrationTest.java b/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v1/node/GetIdentityIntegrationTest.java index 80dee7c8c9d..b87080cb752 100644 --- a/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v1/node/GetIdentityIntegrationTest.java +++ b/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v1/node/GetIdentityIntegrationTest.java @@ -49,7 +49,7 @@ void setup() { when(eth2P2PNetwork.getNodeId()).thenReturn(node1); when(eth2P2PNetwork.getEnr()).thenReturn(Optional.of(enr)); when(eth2P2PNetwork.getNodeAddresses()).thenReturn(List.of(address)); - when(eth2P2PNetwork.getDiscoveryAddress()).thenReturn(Optional.of(discoveryAddress)); + when(eth2P2PNetwork.getDiscoveryAddresses()).thenReturn(Optional.of(List.of(discoveryAddress))); } @Test diff --git a/data/provider/src/main/java/tech/pegasys/teku/api/NetworkDataProvider.java b/data/provider/src/main/java/tech/pegasys/teku/api/NetworkDataProvider.java index a15b0d288f3..1e4bbc2757b 100644 --- a/data/provider/src/main/java/tech/pegasys/teku/api/NetworkDataProvider.java +++ b/data/provider/src/main/java/tech/pegasys/teku/api/NetworkDataProvider.java @@ -62,8 +62,7 @@ public List getListeningAddresses() { } public List getDiscoveryAddresses() { - Optional discoveryAddressOptional = network.getDiscoveryAddress(); - return discoveryAddressOptional.map(List::of).orElseGet(List::of); + return network.getDiscoveryAddresses().orElseGet(List::of); } public MetadataMessage getMetadata() { diff --git a/gradle/versions.gradle b/gradle/versions.gradle index ddfdd64f331..820b094d001 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -157,7 +157,7 @@ dependencyManagement { // discovery includes tuweni libraries under a different name so version resolution doesn't work // exclude them here and leave them to be included on the classpath by the version we use - dependency('tech.pegasys.discovery:discovery:22.12.0') { + dependency('tech.pegasys.discovery:discovery:24.6.0') { exclude 'org.apache.tuweni:bytes' exclude 'org.apache.tuweni:crypto' exclude 'org.apache.tuweni:units' diff --git a/infrastructure/io/src/main/java/tech/pegasys/teku/infrastructure/io/IPVersionResolver.java b/infrastructure/io/src/main/java/tech/pegasys/teku/infrastructure/io/IPVersionResolver.java index c5c59ea9c57..f4d4525f738 100644 --- a/infrastructure/io/src/main/java/tech/pegasys/teku/infrastructure/io/IPVersionResolver.java +++ b/infrastructure/io/src/main/java/tech/pegasys/teku/infrastructure/io/IPVersionResolver.java @@ -16,6 +16,7 @@ import java.io.UncheckedIOException; import java.net.Inet6Address; import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.UnknownHostException; public class IPVersionResolver { @@ -39,7 +40,7 @@ public static IPVersion resolve(final String address) { try { final InetAddress inetAddress = InetAddress.getByName(address); return resolve(inetAddress); - } catch (UnknownHostException ex) { + } catch (final UnknownHostException ex) { throw new UncheckedIOException(ex); } } @@ -47,4 +48,8 @@ public static IPVersion resolve(final String address) { public static IPVersion resolve(final InetAddress inetAddress) { return inetAddress instanceof Inet6Address ? IPVersion.IP_V6 : IPVersion.IP_V4; } + + public static IPVersion resolve(final InetSocketAddress inetSocketAddress) { + return resolve(inetSocketAddress.getAddress()); + } } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java index 65400486110..7ded0e1029d 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java @@ -200,7 +200,10 @@ public P2PConfig build() { final NetworkConfig networkConfig = this.networkConfig.build(); discoveryConfig.listenUdpPortDefault(networkConfig.getListenPort()); + discoveryConfig.listenUdpPortIpv6Default(networkConfig.getListenPortIpv6()); discoveryConfig.advertisedUdpPortDefault(OptionalInt.of(networkConfig.getAdvertisedPort())); + discoveryConfig.advertisedUdpPortIpv6Default( + OptionalInt.of(networkConfig.getAdvertisedPortIpv6())); return new P2PConfig( spec, diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryConfig.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryConfig.java index e797ef9c62c..aaf52c88e64 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryConfig.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryConfig.java @@ -15,6 +15,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static tech.pegasys.teku.networking.p2p.network.config.NetworkConfig.DEFAULT_P2P_PORT; +import static tech.pegasys.teku.networking.p2p.network.config.NetworkConfig.DEFAULT_P2P_PORT_IPV6; import java.util.Collections; import java.util.List; @@ -31,7 +32,9 @@ public class DiscoveryConfig { private final boolean isDiscoveryEnabled; private final int listenUdpPort; + private final int listenUpdPortIpv6; private final OptionalInt advertisedUdpPort; + private final OptionalInt advertisedUdpPortIpv6; private final List staticPeers; private final List bootnodes; private final int minPeers; @@ -42,7 +45,9 @@ public class DiscoveryConfig { private DiscoveryConfig( final boolean isDiscoveryEnabled, final int listenUdpPort, + final int listenUpdPortIpv6, final OptionalInt advertisedUdpPort, + final OptionalInt advertisedUdpPortIpv6, final List staticPeers, final List bootnodes, final int minPeers, @@ -51,7 +56,9 @@ private DiscoveryConfig( final boolean siteLocalAddressesEnabled) { this.isDiscoveryEnabled = isDiscoveryEnabled; this.listenUdpPort = listenUdpPort; + this.listenUpdPortIpv6 = listenUpdPortIpv6; this.advertisedUdpPort = advertisedUdpPort; + this.advertisedUdpPortIpv6 = advertisedUdpPortIpv6; this.staticPeers = staticPeers; this.bootnodes = bootnodes; this.minPeers = minPeers; @@ -72,10 +79,18 @@ public int getListenUdpPort() { return listenUdpPort; } + public int getListenUpdPortIpv6() { + return listenUpdPortIpv6; + } + public int getAdvertisedUdpPort() { return advertisedUdpPort.orElse(listenUdpPort); } + public int getAdvertisedUdpPortIpv6() { + return advertisedUdpPortIpv6.orElse(listenUpdPortIpv6); + } + public List getStaticPeers() { return staticPeers; } @@ -108,7 +123,9 @@ public static class Builder { private int maxPeers = DEFAULT_P2P_PEERS_UPPER_BOUND; private OptionalInt minRandomlySelectedPeers = OptionalInt.empty(); private OptionalInt listenUdpPort = OptionalInt.empty(); + private OptionalInt listenUdpPortIpv6 = OptionalInt.empty(); private OptionalInt advertisedUdpPort = OptionalInt.empty(); + private OptionalInt advertisedUdpPortIpv6 = OptionalInt.empty(); private boolean siteLocalAddressesEnabled = DEFAULT_SITE_LOCAL_ADDRESSES_ENABLED; private Builder() {} @@ -119,7 +136,9 @@ public DiscoveryConfig build() { return new DiscoveryConfig( isDiscoveryEnabled, listenUdpPort.orElseThrow(), + listenUdpPortIpv6.orElseThrow(), advertisedUdpPort, + advertisedUdpPortIpv6, staticPeers, bootnodes == null ? Collections.emptyList() : bootnodes, minPeers, @@ -139,6 +158,9 @@ private void initMissingDefaults() { if (listenUdpPort.isEmpty()) { listenUdpPort = OptionalInt.of(DEFAULT_P2P_PORT); } + if (listenUdpPortIpv6.isEmpty()) { + listenUdpPortIpv6 = OptionalInt.of(DEFAULT_P2P_PORT_IPV6); + } } public Builder isDiscoveryEnabled(final Boolean discoveryEnabled) { @@ -148,32 +170,37 @@ public Builder isDiscoveryEnabled(final Boolean discoveryEnabled) { } public Builder listenUdpPort(final int listenUdpPort) { - if (!PortAvailability.isPortValid(listenUdpPort)) { - throw new InvalidConfigurationException( - String.format("Invalid listenUdpPort: %d", listenUdpPort)); - } + validatePort(listenUdpPort, "--p2p-udp-port"); this.listenUdpPort = OptionalInt.of(listenUdpPort); return this; } public Builder listenUdpPortDefault(final int listenUdpPort) { - if (!PortAvailability.isPortValid(listenUdpPort)) { - throw new InvalidConfigurationException( - String.format("Invalid listenUdpPortDefault: %d", listenUdpPort)); - } + validatePort(listenUdpPort, "--p2p-udp-port"); if (this.listenUdpPort.isEmpty()) { this.listenUdpPort = OptionalInt.of(listenUdpPort); } return this; } + public Builder listenUdpPortIpv6(final int listenUdpPortIpv6) { + validatePort(listenUdpPortIpv6, "--Xp2p-udp-port-ipv6"); + this.listenUdpPortIpv6 = OptionalInt.of(listenUdpPortIpv6); + return this; + } + + public Builder listenUdpPortIpv6Default(final int listenUdpPortIpv6) { + validatePort(listenUdpPortIpv6, "--Xp2p-udp-port-ipv6"); + if (this.listenUdpPortIpv6.isEmpty()) { + this.listenUdpPortIpv6 = OptionalInt.of(listenUdpPortIpv6); + } + return this; + } + public Builder advertisedUdpPort(final OptionalInt advertisedUdpPort) { checkNotNull(advertisedUdpPort); if (advertisedUdpPort.isPresent()) { - if (!PortAvailability.isPortValid(advertisedUdpPort.getAsInt())) { - throw new InvalidConfigurationException( - String.format("Invalid advertisedUdpPort: %d", advertisedUdpPort.getAsInt())); - } + validatePort(advertisedUdpPort.getAsInt(), "--p2p-advertised-udp-port"); } this.advertisedUdpPort = advertisedUdpPort; return this; @@ -182,10 +209,7 @@ public Builder advertisedUdpPort(final OptionalInt advertisedUdpPort) { public Builder advertisedUdpPortDefault(final OptionalInt advertisedUdpPort) { checkNotNull(advertisedUdpPort); if (advertisedUdpPort.isPresent()) { - if (!PortAvailability.isPortValid(advertisedUdpPort.getAsInt())) { - throw new InvalidConfigurationException( - String.format("Invalid advertisedUdpPortDefault: %d", advertisedUdpPort.getAsInt())); - } + validatePort(advertisedUdpPort.getAsInt(), "--p2p-advertised-udp-port"); } if (this.advertisedUdpPort.isEmpty()) { this.advertisedUdpPort = advertisedUdpPort; @@ -193,6 +217,26 @@ public Builder advertisedUdpPortDefault(final OptionalInt advertisedUdpPort) { return this; } + public Builder advertisedUdpPortIpv6(final OptionalInt advertisedUdpPortIpv6) { + checkNotNull(advertisedUdpPortIpv6); + if (advertisedUdpPortIpv6.isPresent()) { + validatePort(advertisedUdpPortIpv6.getAsInt(), "--Xp2p-advertised-udp-port-ipv6"); + } + this.advertisedUdpPortIpv6 = advertisedUdpPortIpv6; + return this; + } + + public Builder advertisedUdpPortIpv6Default(final OptionalInt advertisedUdpPortIpv6) { + checkNotNull(advertisedUdpPortIpv6); + if (advertisedUdpPortIpv6.isPresent()) { + validatePort(advertisedUdpPortIpv6.getAsInt(), "--Xp2p-advertised-udp-port-ipv6"); + } + if (this.advertisedUdpPortIpv6.isEmpty()) { + this.advertisedUdpPortIpv6 = advertisedUdpPortIpv6; + } + return this; + } + public Builder staticPeers(final List staticPeers) { checkNotNull(staticPeers); this.staticPeers = staticPeers; @@ -245,5 +289,11 @@ public Builder siteLocalAddressesEnabled(final boolean siteLocalAddressesEnabled this.siteLocalAddressesEnabled = siteLocalAddressesEnabled; return this; } + + private void validatePort(final int port, final String cliOption) { + if (!PortAvailability.isPortValid(port)) { + throw new InvalidConfigurationException(String.format("Invalid %s: %d", cliOption, port)); + } + } } } diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryNetwork.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryNetwork.java index b407839c912..2b14d7f7f66 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryNetwork.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryNetwork.java @@ -14,6 +14,7 @@ package tech.pegasys.teku.networking.p2p.discovery; import java.math.BigInteger; +import java.util.List; import java.util.Optional; import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; @@ -115,8 +116,8 @@ public Optional getDiscoveryNodeId() { } @Override - public Optional getDiscoveryAddress() { - return discoveryService.getDiscoveryAddress(); + public Optional> getDiscoveryAddresses() { + return discoveryService.getDiscoveryAddresses(); } public void setLongTermAttestationSubnetSubscriptions(final Iterable subnetIds) { diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryService.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryService.java index 9e5f3888128..9da44bc27d8 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryService.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryService.java @@ -14,6 +14,7 @@ package tech.pegasys.teku.networking.p2p.discovery; import java.util.Collection; +import java.util.List; import java.util.Optional; import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; @@ -36,7 +37,7 @@ public interface DiscoveryService { Optional getNodeId(); - Optional getDiscoveryAddress(); + Optional> getDiscoveryAddresses(); void updateCustomENRField(String fieldName, Bytes value); } diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/DiscV5Service.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/DiscV5Service.java index e190f7e2a51..c06fd4cafee 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/DiscV5Service.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/DiscV5Service.java @@ -15,7 +15,10 @@ import static java.util.Collections.emptyList; +import com.google.common.base.Preconditions; +import java.net.InetSocketAddress; import java.time.Duration; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Optional; @@ -37,6 +40,8 @@ import tech.pegasys.teku.infrastructure.async.AsyncRunner; import tech.pegasys.teku.infrastructure.async.Cancellable; import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.io.IPVersionResolver; +import tech.pegasys.teku.infrastructure.io.IPVersionResolver.IPVersion; import tech.pegasys.teku.infrastructure.metrics.TekuMetricCategory; import tech.pegasys.teku.networking.p2p.discovery.DiscoveryConfig; import tech.pegasys.teku.networking.p2p.discovery.DiscoveryPeer; @@ -82,27 +87,67 @@ public DiscV5Service( this.localNodePrivateKey = SecretKeyParser.fromLibP2pPrivKey(privateKey); this.currentSchemaDefinitionsSupplier = currentSchemaDefinitionsSupplier; this.nodeRecordConverter = nodeRecordConverter; - // TODO: https://github.com/Consensys/discovery/issues/176 - final String listenAddress = p2pConfig.getNetworkInterfaces().get(0); - final int listenUdpPort = discoConfig.getListenUdpPort(); - final String advertisedAddress = p2pConfig.getAdvertisedIps().get(0); - final int advertisedTcpPort = p2pConfig.getAdvertisedPort(); - final int advertisedUdpPort = discoConfig.getAdvertisedUdpPort(); + final List networkInterfaces = p2pConfig.getNetworkInterfaces(); + Preconditions.checkState( + networkInterfaces.size() == 1 || networkInterfaces.size() == 2, + "The configured network interfaces must be either 1 or 2"); + if (networkInterfaces.size() == 1) { + discoverySystemBuilder.listen(networkInterfaces.get(0), discoConfig.getListenUdpPort()); + } else { + // IPv4 and IPv6 (dual-stack) + final InetSocketAddress[] listenAddresses = + networkInterfaces.stream() + .map( + networkInterface -> { + final int listenUdpPort = + switch (IPVersionResolver.resolve(networkInterface)) { + case IP_V4 -> discoConfig.getListenUdpPort(); + case IP_V6 -> discoConfig.getListenUpdPortIpv6(); + }; + return new InetSocketAddress(networkInterface, listenUdpPort); + }) + .toArray(InetSocketAddress[]::new); + discoverySystemBuilder.listen(listenAddresses); + } final UInt64 seqNo = kvStore.get(SEQ_NO_STORE_KEY).map(UInt64::fromBytes).orElse(UInt64.ZERO).add(1); - final NewAddressHandler maybeUpdateNodeRecordHandler = - maybeUpdateNodeRecord(p2pConfig.hasUserExplicitlySetAdvertisedIp(), advertisedTcpPort); + final NewAddressHandler maybeUpdateNodeRecordHandler = maybeUpdateNodeRecord(p2pConfig); this.bootnodes = discoConfig.getBootnodes().stream().map(NodeRecordFactory.DEFAULT::fromEnr).toList(); final NodeRecordBuilder nodeRecordBuilder = new NodeRecordBuilder().secretKey(localNodePrivateKey).seq(seqNo); - if (p2pConfig.hasUserExplicitlySetAdvertisedIp()) { - nodeRecordBuilder.address(advertisedAddress, advertisedUdpPort, advertisedTcpPort); + if (p2pConfig.hasUserExplicitlySetAdvertisedIps()) { + final List advertisedIps = p2pConfig.getAdvertisedIps(); + Preconditions.checkState( + advertisedIps.size() == 1 || advertisedIps.size() == 2, + "The configured advertised IPs must be either 1 or 2"); + if (advertisedIps.size() == 1) { + nodeRecordBuilder.address( + advertisedIps.get(0), + discoConfig.getAdvertisedUdpPort(), + p2pConfig.getAdvertisedPort()); + } else { + // IPv4 and IPv6 (dual-stack) + advertisedIps.forEach( + advertisedIp -> { + final IPVersion ipVersion = IPVersionResolver.resolve(advertisedIp); + final int advertisedUdpPort = + switch (ipVersion) { + case IP_V4 -> discoConfig.getAdvertisedUdpPort(); + case IP_V6 -> discoConfig.getAdvertisedUdpPortIpv6(); + }; + final int advertisedTcpPort = + switch (ipVersion) { + case IP_V4 -> p2pConfig.getAdvertisedPort(); + case IP_V6 -> p2pConfig.getAdvertisedPortIpv6(); + }; + nodeRecordBuilder.address(advertisedIp, advertisedUdpPort, advertisedTcpPort); + }); + } } final NodeRecord localNodeRecord = nodeRecordBuilder.build(); this.discoverySystem = discoverySystemBuilder - .listen(listenAddress, listenUdpPort) .secretKey(localNodePrivateKey) .bootnodes(bootnodes) .localNodeRecord(localNodeRecord) @@ -121,15 +166,25 @@ public DiscV5Service( () -> discoverySystem.getBucketStats().getTotalLiveNodeCount()); } - private NewAddressHandler maybeUpdateNodeRecord( - final boolean userExplicitlySetAdvertisedIpOrPort, final int advertisedTcpPort) { - if (userExplicitlySetAdvertisedIpOrPort) { + private NewAddressHandler maybeUpdateNodeRecord(final NetworkConfig p2pConfig) { + if (p2pConfig.hasUserExplicitlySetAdvertisedIps()) { return (oldRecord, newAddress) -> Optional.of(oldRecord); } else { - return (oldRecord, newAddress) -> - Optional.of( - oldRecord.withNewAddress( - newAddress, Optional.of(advertisedTcpPort), localNodePrivateKey)); + return (oldRecord, newAddress) -> { + final int newTcpPort; + if (p2pConfig.getNetworkInterfaces().size() == 1) { + newTcpPort = p2pConfig.getAdvertisedPort(); + } else { + // IPv4 and IPv6 (dual-stack) + newTcpPort = + switch (IPVersionResolver.resolve(newAddress)) { + case IP_V4 -> p2pConfig.getAdvertisedPort(); + case IP_V6 -> p2pConfig.getAdvertisedPortIpv6(); + }; + } + return Optional.of( + oldRecord.withNewAddress(newAddress, Optional.of(newTcpPort), localNodePrivateKey)); + }; } } @@ -206,20 +261,31 @@ public Optional getNodeId() { } @Override - public Optional getDiscoveryAddress() { + public Optional> getDiscoveryAddresses() { final NodeRecord nodeRecord = discoverySystem.getLocalNodeRecord(); - if (nodeRecord.getUdpAddress().isEmpty()) { + final List updAddresses = new ArrayList<>(); + nodeRecord.getUdpAddress().ifPresent(updAddresses::add); + nodeRecord.getUdp6Address().ifPresent(updAddresses::add); + if (updAddresses.isEmpty()) { return Optional.empty(); } - final DiscoveryPeer discoveryPeer = - new DiscoveryPeer( - (Bytes) nodeRecord.get(EnrField.PKEY_SECP256K1), - nodeRecord.getUdpAddress().get(), - Optional.empty(), - currentSchemaDefinitionsSupplier.getAttnetsENRFieldSchema().getDefault(), - currentSchemaDefinitionsSupplier.getSyncnetsENRFieldSchema().getDefault()); - - return Optional.of(MultiaddrUtil.fromDiscoveryPeerAsUdp(discoveryPeer).toString()); + final List discoveryAddresses = + updAddresses.stream() + .map( + updAddress -> { + final DiscoveryPeer discoveryPeer = + new DiscoveryPeer( + (Bytes) nodeRecord.get(EnrField.PKEY_SECP256K1), + updAddress, + Optional.empty(), + currentSchemaDefinitionsSupplier.getAttnetsENRFieldSchema().getDefault(), + currentSchemaDefinitionsSupplier + .getSyncnetsENRFieldSchema() + .getDefault()); + return MultiaddrUtil.fromDiscoveryPeerAsUdp(discoveryPeer).toString(); + }) + .toList(); + return Optional.of(discoveryAddresses); } @Override diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverter.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverter.java index 6889becad42..3d1f05d253a 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverter.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverter.java @@ -36,8 +36,10 @@ public class NodeRecordConverter { public Optional convertToDiscoveryPeer( final NodeRecord nodeRecord, final SchemaDefinitions schemaDefinitions) { + // TODO: https://github.com/Consensys/teku/issues/8069 return nodeRecord .getTcpAddress() + .or(nodeRecord::getTcp6Address) .map(address -> socketAddressToDiscoveryPeer(schemaDefinitions, nodeRecord, address)); } diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/noop/NoOpDiscoveryService.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/noop/NoOpDiscoveryService.java index 10b842c931f..579988acd23 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/noop/NoOpDiscoveryService.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/noop/NoOpDiscoveryService.java @@ -16,6 +16,7 @@ import java.math.BigInteger; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Optional; import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; @@ -56,7 +57,7 @@ public Optional getNodeId() { } @Override - public Optional getDiscoveryAddress() { + public Optional> getDiscoveryAddresses() { return Optional.empty(); } diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/libp2p/LibP2PNetwork.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/libp2p/LibP2PNetwork.java index fd9fe375454..b13f73fd6ad 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/libp2p/LibP2PNetwork.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/libp2p/LibP2PNetwork.java @@ -186,7 +186,7 @@ public Optional getDiscoveryNodeId() { } @Override - public Optional getDiscoveryAddress() { + public Optional> getDiscoveryAddresses() { return Optional.empty(); } diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/mock/MockP2PNetwork.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/mock/MockP2PNetwork.java index dfeeb88f048..ba0348855d5 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/mock/MockP2PNetwork.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/mock/MockP2PNetwork.java @@ -115,7 +115,7 @@ public Optional getDiscoveryNodeId() { } @Override - public Optional getDiscoveryAddress() { + public Optional> getDiscoveryAddresses() { return Optional.empty(); } diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/network/DelegatingP2PNetwork.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/network/DelegatingP2PNetwork.java index c8be5f1c3cc..cb121827b21 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/network/DelegatingP2PNetwork.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/network/DelegatingP2PNetwork.java @@ -91,8 +91,8 @@ public Optional getDiscoveryNodeId() { } @Override - public Optional getDiscoveryAddress() { - return network.getDiscoveryAddress(); + public Optional> getDiscoveryAddresses() { + return network.getDiscoveryAddresses(); } @Override diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/network/P2PNetwork.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/network/P2PNetwork.java index c0cc55d4f3a..76bc518fdd0 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/network/P2PNetwork.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/network/P2PNetwork.java @@ -98,7 +98,7 @@ enum State { Optional getDiscoveryNodeId(); - Optional getDiscoveryAddress(); + Optional> getDiscoveryAddresses(); Optional> getDiscoveryNetwork(); diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/network/config/NetworkConfig.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/network/config/NetworkConfig.java index b58902591be..2dce9abd3d5 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/network/config/NetworkConfig.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/network/config/NetworkConfig.java @@ -109,7 +109,7 @@ public List getAdvertisedIps() { .toList(); } - public boolean hasUserExplicitlySetAdvertisedIp() { + public boolean hasUserExplicitlySetAdvertisedIps() { return advertisedIps.isPresent(); } diff --git a/teku/src/main/java/tech/pegasys/teku/cli/options/P2POptions.java b/teku/src/main/java/tech/pegasys/teku/cli/options/P2POptions.java index a41589b0368..ee5fad63667 100644 --- a/teku/src/main/java/tech/pegasys/teku/cli/options/P2POptions.java +++ b/teku/src/main/java/tech/pegasys/teku/cli/options/P2POptions.java @@ -73,6 +73,17 @@ public class P2POptions { arity = "1") private Integer p2pUdpPort; + @Option( + names = {"--Xp2p-udp-port-ipv6"}, + paramLabel = "", + description = + """ + IPv6 UDP port used for discovery. This port is only used when listening over both IPv4 and IPv6. + If listening over only IPv6, the value of --p2p-udp-port will be used. The default is the port specified in --Xp2p-port-ipv6""", + hidden = true, + arity = "1") + private Integer p2pUdpPortIpv6; + @Option( names = {"--p2p-discovery-enabled"}, paramLabel = "", @@ -124,6 +135,17 @@ public class P2POptions { arity = "1") private Integer p2pAdvertisedUdpPort; + @Option( + names = {"--Xp2p-advertised-udp-port-ipv6"}, + paramLabel = "", + description = + """ + Advertised IPv6 UDP port to external peers. This port is only used when advertising both IPv4 and IPv6 addresses. + If advertising only an IPv6 address, the value of ---p2p-advertised-udp-port will be used. The default is the port specified in --Xp2p-advertised-port-ipv6""", + hidden = true, + arity = "1") + private Integer p2pAdvertisedUdpPortIpv6; + @Option( names = {"--p2p-private-key-file"}, paramLabel = "", @@ -384,9 +406,15 @@ public void configure(final TekuConfiguration.Builder builder) { if (p2pUdpPort != null) { d.listenUdpPort(p2pUdpPort); } + if (p2pUdpPortIpv6 != null) { + d.listenUdpPortIpv6(p2pUdpPortIpv6); + } if (p2pAdvertisedUdpPort != null) { d.advertisedUdpPort(OptionalInt.of(p2pAdvertisedUdpPort)); } + if (p2pAdvertisedUdpPortIpv6 != null) { + d.advertisedUdpPortIpv6(OptionalInt.of(p2pAdvertisedPortIpv6)); + } d.isDiscoveryEnabled(p2pDiscoveryEnabled) .staticPeers(p2pStaticPeers) .minPeers(getP2pLowerBound()) diff --git a/teku/src/test/java/tech/pegasys/teku/cli/options/P2POptionsTest.java b/teku/src/test/java/tech/pegasys/teku/cli/options/P2POptionsTest.java index ab0014bfe85..c56c0b552a7 100644 --- a/teku/src/test/java/tech/pegasys/teku/cli/options/P2POptionsTest.java +++ b/teku/src/test/java/tech/pegasys/teku/cli/options/P2POptionsTest.java @@ -109,7 +109,7 @@ void p2pUdpPort_shouldOverrideP2pPortWhenBothSet() { @Test public void advertisedIps_shouldDefaultToEmpty() { final NetworkConfig config = getTekuConfigurationFromArguments().network(); - assertThat(config.hasUserExplicitlySetAdvertisedIp()).isFalse(); + assertThat(config.hasUserExplicitlySetAdvertisedIps()).isFalse(); } @Test