diff --git a/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java b/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java index 14d7232c37d..dcfe3e7a4d9 100644 --- a/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java @@ -350,6 +350,7 @@ public Runner build() { bootstrap = ethNetworkConfig.getBootNodes(); } discoveryConfiguration.setBootnodes(bootstrap); + discoveryConfiguration.setDnsDiscoveryURL(ethNetworkConfig.getDnsDiscoveryUrl()); } else { discoveryConfiguration.setActive(false); } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/config/EthNetworkConfig.java b/besu/src/main/java/org/hyperledger/besu/cli/config/EthNetworkConfig.java index b0bbc749a76..d427ddcce9e 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/config/EthNetworkConfig.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/config/EthNetworkConfig.java @@ -17,10 +17,13 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration.CLASSIC_BOOTSTRAP_NODES; import static org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration.GOERLI_BOOTSTRAP_NODES; +import static org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration.GOERLI_DISCOVERY_URL; import static org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration.KOTTI_BOOTSTRAP_NODES; import static org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration.MAINNET_BOOTSTRAP_NODES; +import static org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration.MAINNET_DISCOVERY_URL; import static org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration.MORDOR_BOOTSTRAP_NODES; import static org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration.RINKEBY_BOOTSTRAP_NODES; +import static org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration.RINKEBY_DISCOVERY_URL; import static org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration.ROPSTEN_BOOTSTRAP_NODES; import static org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration.YOLO_V2_BOOTSTRAP_NODES; @@ -59,14 +62,19 @@ public class EthNetworkConfig { private final String genesisConfig; private final BigInteger networkId; private final List bootNodes; + private final String dnsDiscoveryUrl; public EthNetworkConfig( - final String genesisConfig, final BigInteger networkId, final List bootNodes) { + final String genesisConfig, + final BigInteger networkId, + final List bootNodes, + final String dnsDiscoveryUrl) { Preconditions.checkNotNull(genesisConfig); Preconditions.checkNotNull(bootNodes); this.genesisConfig = genesisConfig; this.networkId = networkId; this.bootNodes = bootNodes; + this.dnsDiscoveryUrl = dnsDiscoveryUrl; } public String getGenesisConfig() { @@ -81,6 +89,10 @@ public List getBootNodes() { return bootNodes; } + public String getDnsDiscoveryUrl() { + return dnsDiscoveryUrl; + } + @Override public boolean equals(final Object o) { if (this == o) { @@ -92,12 +104,13 @@ public boolean equals(final Object o) { final EthNetworkConfig that = (EthNetworkConfig) o; return networkId.equals(that.networkId) && Objects.equals(genesisConfig, that.genesisConfig) - && Objects.equals(bootNodes, that.bootNodes); + && Objects.equals(bootNodes, that.bootNodes) + && Objects.equals(dnsDiscoveryUrl, that.dnsDiscoveryUrl); } @Override public int hashCode() { - return Objects.hash(genesisConfig, networkId, bootNodes); + return Objects.hash(genesisConfig, networkId, bootNodes, dnsDiscoveryUrl); } @Override @@ -109,6 +122,8 @@ public String toString() { + networkId + ", bootNodes=" + bootNodes + + ", dnsDiscoveryUrl=" + + dnsDiscoveryUrl + '}'; } @@ -116,31 +131,41 @@ public static EthNetworkConfig getNetworkConfig(final NetworkName networkName) { switch (networkName) { case ROPSTEN: return new EthNetworkConfig( - jsonConfig(ROPSTEN_GENESIS), ROPSTEN_NETWORK_ID, ROPSTEN_BOOTSTRAP_NODES); + jsonConfig(ROPSTEN_GENESIS), ROPSTEN_NETWORK_ID, ROPSTEN_BOOTSTRAP_NODES, null); case RINKEBY: return new EthNetworkConfig( - jsonConfig(RINKEBY_GENESIS), RINKEBY_NETWORK_ID, RINKEBY_BOOTSTRAP_NODES); + jsonConfig(RINKEBY_GENESIS), + RINKEBY_NETWORK_ID, + RINKEBY_BOOTSTRAP_NODES, + RINKEBY_DISCOVERY_URL); case GOERLI: return new EthNetworkConfig( - jsonConfig(GOERLI_GENESIS), GOERLI_NETWORK_ID, GOERLI_BOOTSTRAP_NODES); + jsonConfig(GOERLI_GENESIS), + GOERLI_NETWORK_ID, + GOERLI_BOOTSTRAP_NODES, + GOERLI_DISCOVERY_URL); case DEV: - return new EthNetworkConfig(jsonConfig(DEV_GENESIS), DEV_NETWORK_ID, new ArrayList<>()); + return new EthNetworkConfig( + jsonConfig(DEV_GENESIS), DEV_NETWORK_ID, new ArrayList<>(), null); case CLASSIC: return new EthNetworkConfig( - jsonConfig(CLASSIC_GENESIS), CLASSIC_NETWORK_ID, CLASSIC_BOOTSTRAP_NODES); + jsonConfig(CLASSIC_GENESIS), CLASSIC_NETWORK_ID, CLASSIC_BOOTSTRAP_NODES, null); case KOTTI: return new EthNetworkConfig( - jsonConfig(KOTTI_GENESIS), KOTTI_NETWORK_ID, KOTTI_BOOTSTRAP_NODES); + jsonConfig(KOTTI_GENESIS), KOTTI_NETWORK_ID, KOTTI_BOOTSTRAP_NODES, null); case MORDOR: return new EthNetworkConfig( - jsonConfig(MORDOR_GENESIS), MORDOR_NETWORK_ID, MORDOR_BOOTSTRAP_NODES); + jsonConfig(MORDOR_GENESIS), MORDOR_NETWORK_ID, MORDOR_BOOTSTRAP_NODES, null); case YOLO_V2: return new EthNetworkConfig( - jsonConfig(YOLO_GENESIS), YOLO_V2_NETWORK_ID, YOLO_V2_BOOTSTRAP_NODES); + jsonConfig(YOLO_GENESIS), YOLO_V2_NETWORK_ID, YOLO_V2_BOOTSTRAP_NODES, null); case MAINNET: default: return new EthNetworkConfig( - jsonConfig(MAINNET_GENESIS), MAINNET_NETWORK_ID, MAINNET_BOOTSTRAP_NODES); + jsonConfig(MAINNET_GENESIS), + MAINNET_NETWORK_ID, + MAINNET_BOOTSTRAP_NODES, + MAINNET_DISCOVERY_URL); } } @@ -180,6 +205,7 @@ public static String jsonConfig(final NetworkName network) { public static class Builder { + private final String dnsDiscoveryUrl; private String genesisConfig; private BigInteger networkId; private List bootNodes; @@ -188,6 +214,7 @@ public Builder(final EthNetworkConfig ethNetworkConfig) { this.genesisConfig = ethNetworkConfig.genesisConfig; this.networkId = ethNetworkConfig.networkId; this.bootNodes = ethNetworkConfig.bootNodes; + this.dnsDiscoveryUrl = ethNetworkConfig.dnsDiscoveryUrl; } public Builder setGenesisConfig(final String genesisConfig) { @@ -206,7 +233,7 @@ public Builder setBootNodes(final List bootNodes) { } public EthNetworkConfig build() { - return new EthNetworkConfig(genesisConfig, networkId, bootNodes); + return new EthNetworkConfig(genesisConfig, networkId, bootNodes, dnsDiscoveryUrl); } } } diff --git a/besu/src/test/java/org/hyperledger/besu/RunnerTest.java b/besu/src/test/java/org/hyperledger/besu/RunnerTest.java index b3eb662f39d..0d8aa45efc6 100644 --- a/besu/src/test/java/org/hyperledger/besu/RunnerTest.java +++ b/besu/src/test/java/org/hyperledger/besu/RunnerTest.java @@ -254,7 +254,10 @@ private void syncFromGenesis(final SyncMode mode, final GenesisConfigFile genesi final EnodeURL enode = runnerAhead.getLocalEnode().get(); final EthNetworkConfig behindEthNetworkConfiguration = new EthNetworkConfig( - EthNetworkConfig.jsonConfig(DEV), DEV_NETWORK_ID, Collections.singletonList(enode)); + EthNetworkConfig.jsonConfig(DEV), + DEV_NETWORK_ID, + Collections.singletonList(enode), + null); runnerBehind = runnerBuilder .besuController(controllerBehind) diff --git a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java index f42af0d10f3..b4584766939 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java @@ -31,6 +31,7 @@ import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.PERM; import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.WEB3; import static org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration.MAINNET_BOOTSTRAP_NODES; +import static org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration.MAINNET_DISCOVERY_URL; import static org.hyperledger.besu.nat.kubernetes.KubernetesNatManager.DEFAULT_BESU_SERVICE_NAME_FILTER; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -173,7 +174,8 @@ public void callingBesuCommandWithoutOptionsMustSyncWithDefaultValues() throws E new EthNetworkConfig( EthNetworkConfig.jsonConfig(MAINNET), EthNetworkConfig.MAINNET_NETWORK_ID, - MAINNET_BOOTSTRAP_NODES)); + MAINNET_BOOTSTRAP_NODES, + MAINNET_DISCOVERY_URL)); verify(mockRunnerBuilder).p2pAdvertisedHost(eq("127.0.0.1")); verify(mockRunnerBuilder).p2pListenPort(eq(30303)); verify(mockRunnerBuilder).maxPeers(eq(25)); @@ -784,7 +786,8 @@ public void noOverrideDefaultValuesIfKeyIsNotPresentInConfigFile() throws IOExce new EthNetworkConfig( EthNetworkConfig.jsonConfig(MAINNET), EthNetworkConfig.MAINNET_NETWORK_ID, - MAINNET_BOOTSTRAP_NODES)); + MAINNET_BOOTSTRAP_NODES, + MAINNET_DISCOVERY_URL)); verify(mockRunnerBuilder).p2pAdvertisedHost(eq("127.0.0.1")); verify(mockRunnerBuilder).p2pListenPort(eq(30303)); verify(mockRunnerBuilder).maxPeers(eq(25)); diff --git a/ethereum/p2p/build.gradle b/ethereum/p2p/build.gradle index 054699e20de..72b946ff8a2 100644 --- a/ethereum/p2p/build.gradle +++ b/ethereum/p2p/build.gradle @@ -35,11 +35,18 @@ dependencies { implementation project(':nat') implementation 'com.google.guava:guava' + implementation 'dnsjava:dnsjava' implementation 'io.prometheus:simpleclient' implementation 'io.vertx:vertx-core' implementation 'org.apache.logging.log4j:log4j-api' implementation 'org.apache.tuweni:bytes' + implementation 'org.apache.tuweni:crypto' + implementation 'org.apache.tuweni:devp2p' + implementation 'org.apache.tuweni:dns-discovery' + implementation 'org.apache.tuweni:io' + implementation 'org.apache.tuweni:rlp' implementation 'org.apache.tuweni:units' + implementation 'org.jetbrains.kotlin:kotlin-stdlib' implementation 'org.xerial.snappy:snappy-java' annotationProcessor "org.immutables:value" diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/config/DiscoveryConfiguration.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/config/DiscoveryConfiguration.java index 50449441ea9..a67ad04d9fd 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/config/DiscoveryConfiguration.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/config/DiscoveryConfiguration.java @@ -27,7 +27,14 @@ import java.util.stream.Stream; public class DiscoveryConfiguration { - public static List MAINNET_BOOTSTRAP_NODES = + public static final String GOERLI_DISCOVERY_URL = + "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@all.goerli.ethdisco.net"; + public static final String MAINNET_DISCOVERY_URL = + "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@all.mainnet.ethdisco.net"; + public static final String RINKEBY_DISCOVERY_URL = + "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@all.rinkeby.ethdisco.net"; + + public static final List MAINNET_BOOTSTRAP_NODES = Collections.unmodifiableList( Stream.of( // Ethereum Foundation Bootnodes @@ -52,7 +59,7 @@ public class DiscoveryConfiguration { ) .map(EnodeURL::fromString) .collect(toList())); - public static List RINKEBY_BOOTSTRAP_NODES = + public static final List RINKEBY_BOOTSTRAP_NODES = Collections.unmodifiableList( Stream.of( "enode://a24ac7c5484ef4ed0c5eb2d36620ba4e4aa13b8c84684e1b4aab0cebea2ae45cb4d375b77eab56516d34bfbd3c1a833fc51296ff084b770b94fb9028c4d25ccf@52.169.42.101:30303", @@ -60,7 +67,7 @@ public class DiscoveryConfiguration { "enode://b6b28890b006743680c52e64e0d16db57f28124885595fa03a562be1d2bf0f3a1da297d56b13da25fb992888fd556d4c1a27b1f39d531bde7de1921c90061cc6@159.89.28.211:30303") .map(EnodeURL::fromString) .collect(toList())); - public static List ROPSTEN_BOOTSTRAP_NODES = + public static final List ROPSTEN_BOOTSTRAP_NODES = Collections.unmodifiableList( Stream.of( "enode://6332792c4a00e3e4ee0926ed89e0d27ef985424d97b6a45bf0f23e51f0dcb5e66b875777506458aea7af6f9e4ffb69f43f3778ee73c81ed9d34c51c4b16b0b0f@52.232.243.152:30303", @@ -70,7 +77,7 @@ public class DiscoveryConfiguration { .map(EnodeURL::fromString) .collect(toList())); - public static List GOERLI_BOOTSTRAP_NODES = + public static final List GOERLI_BOOTSTRAP_NODES = Collections.unmodifiableList( Stream.of( "enode://011f758e6552d105183b1761c5e2dea0111bc20fd5f6422bc7f91e0fabbec9a6595caf6239b37feb773dddd3f87240d99d859431891e4a642cf2a0a9e6cbb98a@51.141.78.53:30303", @@ -89,7 +96,7 @@ public class DiscoveryConfiguration { .map(EnodeURL::fromString) .collect(toList())); - public static List CLASSIC_BOOTSTRAP_NODES = + public static final List CLASSIC_BOOTSTRAP_NODES = Collections.unmodifiableList( Stream.of( "enode://158ac5a4817265d0d8b977660b3dbe9abee5694ed212f7091cbf784ddf47623ed015e1cb54594d10c1c46118747ddabe86ebf569cf24ae91f2daa0f1adaae390@159.203.56.33:30303", @@ -114,7 +121,7 @@ public class DiscoveryConfiguration { .map(EnodeURL::fromString) .collect(toList())); - public static List KOTTI_BOOTSTRAP_NODES = + public static final List KOTTI_BOOTSTRAP_NODES = Collections.unmodifiableList( Stream.of( // Authority Nodes @@ -134,7 +141,7 @@ public class DiscoveryConfiguration { .map(EnodeURL::fromString) .collect(toList())); - public static List MORDOR_BOOTSTRAP_NODES = + public static final List MORDOR_BOOTSTRAP_NODES = Collections.unmodifiableList( Stream.of( "enode://642cf9650dd8869d42525dbf6858012e3b4d64f475e733847ab6f7742341a4397414865d953874e8f5ed91b0e4e1c533dee14ad1d6bb276a5459b2471460ff0d@157.230.152.87:30303", // @meowbits Mordor @@ -169,7 +176,7 @@ public class DiscoveryConfiguration { .map(EnodeURL::fromString) .collect(toList())); - public static List YOLO_V2_BOOTSTRAP_NODES = + public static final List YOLO_V2_BOOTSTRAP_NODES = Collections.unmodifiableList( Stream.of( "enode://9e1096aa59862a6f164994cb5cb16f5124d6c992cdbf4535ff7dea43ea1512afe5448dca9df1b7ab0726129603f1a3336b631e4d7a1a44c94daddd03241587f9@3.9.20.133:30303") @@ -182,6 +189,7 @@ public class DiscoveryConfiguration { private String advertisedHost = "127.0.0.1"; private int bucketSize = 16; private List bootnodes = new ArrayList<>(); + private String dnsDiscoveryURL; public static DiscoveryConfiguration create() { return new DiscoveryConfiguration(); @@ -255,6 +263,14 @@ public DiscoveryConfiguration setBucketSize(final int bucketSize) { return this; } + public String getDNSDiscoveryURL() { + return dnsDiscoveryURL; + } + + public void setDnsDiscoveryURL(final String dnsDiscoveryURL) { + this.dnsDiscoveryURL = dnsDiscoveryURL; + } + @Override public boolean equals(final Object o) { if (o == this) { @@ -269,12 +285,14 @@ public boolean equals(final Object o) { && bucketSize == that.bucketSize && Objects.equals(bindHost, that.bindHost) && Objects.equals(advertisedHost, that.advertisedHost) - && Objects.equals(bootnodes, that.bootnodes); + && Objects.equals(bootnodes, that.bootnodes) + && Objects.equals(dnsDiscoveryURL, that.dnsDiscoveryURL); } @Override public int hashCode() { - return Objects.hash(active, bindHost, bindPort, advertisedHost, bucketSize, bootnodes); + return Objects.hash( + active, bindHost, bindPort, advertisedHost, bucketSize, bootnodes, dnsDiscoveryURL); } @Override @@ -294,6 +312,8 @@ public String toString() { + bucketSize + ", bootnodes=" + bootnodes + + ", dnsDiscoveryURL=" + + dnsDiscoveryURL + '}'; } } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetwork.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetwork.java index e010cbc7131..28f5b005b44 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetwork.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetwork.java @@ -49,6 +49,7 @@ import org.hyperledger.besu.plugin.services.MetricsSystem; import java.time.Duration; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; @@ -61,6 +62,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -69,6 +71,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.devp2p.EthereumNodeRecord; +import org.apache.tuweni.discovery.DNSDaemon; /** * The peer network service (defunct PeerNetworkingService) is the entrypoint to the peer-to-peer @@ -136,6 +140,9 @@ public class DefaultP2PNetwork implements P2PNetwork { private final CountDownLatch shutdownLatch = new CountDownLatch(2); private final Duration shutdownTimeout = Duration.ofMinutes(1); + private final AtomicReference> dnsPeers = new AtomicReference<>(); + private DNSDaemon dnsDaemon; + /** * Creates a peer networking service for production purposes. * @@ -163,7 +170,6 @@ public class DefaultP2PNetwork implements P2PNetwork { final NatService natService, final MaintainedPeers maintainedPeers, final PeerReputationManager reputationManager) { - this.localNode = localNode; this.peerDiscoveryAgent = peerDiscoveryAgent; this.rlpxAgent = rlpxAgent; @@ -193,6 +199,30 @@ public void start() { final String address = config.getDiscovery().getAdvertisedHost(); final int configuredDiscoveryPort = config.getDiscovery().getBindPort(); final int configuredRlpxPort = config.getRlpx().getBindPort(); + if (config.getDiscovery().getDNSDiscoveryURL() != null) { + LOG.info("Starting DNS discovery with URL {}", config.getDiscovery().getDNSDiscoveryURL()); + dnsDaemon = new DNSDaemon(config.getDiscovery().getDNSDiscoveryURL()); + dnsDaemon + .getListeners() + .add( + (seq, records) -> { + List peers = new ArrayList<>(); + for (EthereumNodeRecord enr : records) { + EnodeURL enodeURL = + EnodeURL.builder() + .ipAddress(enr.ip()) + .nodeId(enr.publicKey().bytes()) + .discoveryPort(Optional.ofNullable(enr.udp())) + .listeningPort(Optional.ofNullable(enr.tcp())) + .build(); + DiscoveryPeer peer = DiscoveryPeer.fromEnode(enodeURL); + peers.add(peer); + rlpxAgent.connect(peer); + } + dnsPeers.set(peers); + return null; + }); + } final int listeningPort = rlpxAgent.start().join(); final int discoveryPort = @@ -235,6 +265,10 @@ public void stop() { return; } + if (dnsDaemon != null) { + dnsDaemon.close(); + } + peerConnectionScheduler.shutdownNow(); peerDiscoveryAgent.stop().whenComplete((res, err) -> shutdownLatch.countDown()); rlpxAgent.stop().whenComplete((res, err) -> shutdownLatch.countDown()); @@ -306,6 +340,10 @@ public Collection getPeers() { @Override public Stream streamDiscoveredPeers() { + List peers = dnsPeers.get(); + if (peers != null) { + return Stream.concat(peerDiscoveryAgent.streamDiscoveredPeers(), peers.stream()); + } return peerDiscoveryAgent.streamDiscoveredPeers(); } diff --git a/gradle/versions.gradle b/gradle/versions.gradle index a8c41ed6d08..62ed5cb1d92 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -43,6 +43,8 @@ dependencyManagement { dependency 'commons-io:commons-io:2.7' + dependency 'dnsjava:dnsjava:3.0.2' + dependency 'info.picocli:picocli:4.5.0' dependency 'io.grpc:grpc-netty:1.33.0' @@ -89,7 +91,10 @@ dependencyManagement { dependency 'org.apache.tuweni:bytes:1.2.0' dependency 'org.apache.tuweni:config:1.2.0' dependency 'org.apache.tuweni:crypto:1.2.0' + dependency 'org.apache.tuweni:devp2p:1.2.0' + dependency 'org.apache.tuweni:dns-discovery:1.2.0' dependency 'org.apache.tuweni:io:1.2.0' + dependency 'org.apache.tuweni:rlp:1.2.0' dependency 'org.apache.tuweni:toml:1.2.0' dependency 'org.apache.tuweni:units:1.2.0' dependency 'org.apache.tuweni:net:1.2.0' @@ -106,6 +111,8 @@ dependencyManagement { dependency 'org.java-websocket:Java-WebSocket:1.5.1' + dependency 'org.jetbrains.kotlin:kotlin-stdlib:1.3.20' + dependency 'org.jupnp:org.jupnp.support:2.5.2' dependency 'org.jupnp:org.jupnp:2.5.2'