From ea7545f0efc18daffb8dc56035fc85444e999a54 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Wed, 22 May 2024 16:30:57 +1000 Subject: [PATCH 01/26] feat: Use besu-dns-discovery -- Use besu-dns-discovery which is a fork of Tuweni dns discovery Signed-off-by: Usman Saleem --- CHANGELOG.md | 1 + ethereum/p2p/build.gradle | 2 +- .../p2p/network/DefaultP2PNetwork.java | 4 +- gradle/verification-metadata.xml | 42 +++++++++++++++++++ gradle/versions.gradle | 5 ++- 5 files changed, 50 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 303ec891e1e..8b973e7cc1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Default bonsai to use full-flat db and code-storage-by-code-hash [#6984](https://github.com/hyperledger/besu/pull/6894) - New RPC methods miner_setExtraData and miner_getExtraData [#7078](https://github.com/hyperledger/besu/pull/7078) - Disconnect peers that have multiple discovery ports since they give us bad neighbours [#7089](https://github.com/hyperledger/besu/pull/7089) +- Use ConsenSys besu-dns-discovery module which is a fork of Tuweni DNS discovery with performance related enhancements. ### Bug fixes - Fix parsing `gasLimit` parameter when its value is > `Long.MAX_VALUE` [#7116](https://github.com/hyperledger/besu/pull/7116) diff --git a/ethereum/p2p/build.gradle b/ethereum/p2p/build.gradle index 7f56178978d..a20b947a09d 100644 --- a/ethereum/p2p/build.gradle +++ b/ethereum/p2p/build.gradle @@ -50,7 +50,7 @@ dependencies { implementation('io.tmio:tuweni-devp2p') { exclude group:'ch.qos.logback', module:'logback-classic' } - implementation('io.tmio:tuweni-dns-discovery'){ + implementation('io.consensys.protocols:besu-dns-discovery'){ exclude group:'ch.qos.logback', module:'logback-classic' } implementation 'io.tmio:tuweni-io' 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 d4a0e3daaba..3aae89ec137 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 @@ -75,11 +75,11 @@ import java.util.stream.Stream; import com.google.common.annotations.VisibleForTesting; +import io.consensys.protocols.util.discovery.DNSDaemon; +import io.consensys.protocols.util.discovery.DNSDaemonListener; import io.vertx.core.Vertx; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.devp2p.EthereumNodeRecord; -import org.apache.tuweni.discovery.DNSDaemon; -import org.apache.tuweni.discovery.DNSDaemonListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 1fd79baf6f2..bcc2fa1e9a1 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -1446,6 +1446,17 @@ + + + + + + + + + + + @@ -1714,6 +1725,11 @@ + + + + + @@ -2723,6 +2739,11 @@ + + + + + @@ -2749,6 +2770,14 @@ + + + + + + + + @@ -2757,11 +2786,24 @@ + + + + + + + + + + + + + diff --git a/gradle/versions.gradle b/gradle/versions.gradle index 427e6e81897..d3938b03330 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -108,7 +108,6 @@ dependencyManagement { entry 'tuweni-concurrent' entry 'tuweni-crypto' entry 'tuweni-devp2p' - entry 'tuweni-dns-discovery' entry 'tuweni-io' entry 'tuweni-net' entry 'tuweni-rlp' @@ -116,6 +115,10 @@ dependencyManagement { entry 'tuweni-units' } + dependencySet(group: 'io.consensys.protocols', version: '1.0.0') { + entry 'besu-dns-discovery' + } + dependencySet(group: 'io.vertx', version: '4.5.4') { entry 'vertx-auth-jwt' entry 'vertx-codegen' From 5bbdeb6c887a893046d73c87e9a5f37d7f3acbc8 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Wed, 22 May 2024 16:35:06 +1000 Subject: [PATCH 02/26] changelog: Update changelog Signed-off-by: Usman Saleem --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b973e7cc1d..f2904a67ea7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ - Default bonsai to use full-flat db and code-storage-by-code-hash [#6984](https://github.com/hyperledger/besu/pull/6894) - New RPC methods miner_setExtraData and miner_getExtraData [#7078](https://github.com/hyperledger/besu/pull/7078) - Disconnect peers that have multiple discovery ports since they give us bad neighbours [#7089](https://github.com/hyperledger/besu/pull/7089) -- Use ConsenSys besu-dns-discovery module which is a fork of Tuweni DNS discovery with performance related enhancements. +- Use ConsenSys [besu-dns-discovery](https://github.com/Consensys/besu-dns-discovery) module which is a fork of Tuweni DNS discovery with performance related enhancements.[#7129](https://github.com/hyperledger/besu/pull/7129) ### Bug fixes - Fix parsing `gasLimit` parameter when its value is > `Long.MAX_VALUE` [#7116](https://github.com/hyperledger/besu/pull/7116) From ea62a8a0a0591624337d35091a6d51e5483742b9 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Wed, 22 May 2024 16:46:22 +1000 Subject: [PATCH 03/26] typo Signed-off-by: Sally MacFarlane --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0cea24bfef..42856d56642 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ - Default bonsai to use full-flat db and code-storage-by-code-hash [#6984](https://github.com/hyperledger/besu/pull/6894) - New RPC methods miner_setExtraData and miner_getExtraData [#7078](https://github.com/hyperledger/besu/pull/7078) - Disconnect peers that have multiple discovery ports since they give us bad neighbours [#7089](https://github.com/hyperledger/besu/pull/7089) -- Use ConsenSys [besu-dns-discovery](https://github.com/Consensys/besu-dns-discovery) module which is a fork of Tuweni DNS discovery with performance related enhancements.[#7129](https://github.com/hyperledger/besu/pull/7129) +- Use Consensys [besu-dns-discovery](https://github.com/Consensys/besu-dns-discovery) module which is a fork of Tuweni DNS discovery with performance related enhancements .[#7129](https://github.com/hyperledger/besu/pull/7129) ### Bug fixes - Fix parsing `gasLimit` parameter when its value is > `Long.MAX_VALUE` [#7116](https://github.com/hyperledger/besu/pull/7116) From 9a780d8eb28ce880a36914d9a0376db855491eba Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Wed, 22 May 2024 19:16:44 +1000 Subject: [PATCH 04/26] test: Fix unit test mocking Signed-off-by: Usman Saleem --- .../besu/ethereum/p2p/network/DefaultP2PNetworkTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetworkTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetworkTest.java index 68a4f76b582..9ed2139a38a 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetworkTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetworkTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import io.vertx.core.impl.ContextInternal; import org.hyperledger.besu.cryptoservices.NodeKey; import org.hyperledger.besu.cryptoservices.NodeKeyUtils; import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; @@ -337,7 +338,7 @@ public void shouldStartDnsDiscoveryWhenDnsURLIsConfigured() { Vertx vertx = mock(Vertx.class); when(vertx.createDnsClient(any())).thenReturn(mock(DnsClient.class)); - when(vertx.getOrCreateContext()).thenReturn(mock(Context.class)); + when(vertx.getOrCreateContext()).thenReturn(mock(ContextInternal.class)); // spy on DefaultP2PNetwork final DefaultP2PNetwork testClass = @@ -360,7 +361,7 @@ public void shouldUseDnsServerOverrideIfPresent() { Vertx vertx = mock(Vertx.class); when(vertx.createDnsClient(any())).thenReturn(mock(DnsClient.class)); - when(vertx.getOrCreateContext()).thenReturn(mock(Context.class)); + when(vertx.getOrCreateContext()).thenReturn(mock(ContextInternal.class)); final DefaultP2PNetwork testClass = (DefaultP2PNetwork) builder().config(dnsConfig).vertx(vertx).build(); From ce46275357fbd59ee26ade2f568248cfbb146ae0 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Wed, 22 May 2024 19:28:23 +1000 Subject: [PATCH 05/26] test: spotless fix Signed-off-by: Usman Saleem --- .../besu/ethereum/p2p/network/DefaultP2PNetworkTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetworkTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetworkTest.java index 9ed2139a38a..c468996a14b 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetworkTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetworkTest.java @@ -26,7 +26,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import io.vertx.core.impl.ContextInternal; import org.hyperledger.besu.cryptoservices.NodeKey; import org.hyperledger.besu.cryptoservices.NodeKeyUtils; import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; @@ -58,9 +57,9 @@ import java.util.concurrent.CompletableFuture; import java.util.stream.Stream; -import io.vertx.core.Context; import io.vertx.core.Vertx; import io.vertx.core.dns.DnsClient; +import io.vertx.core.impl.ContextInternal; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.crypto.SECP256K1; import org.assertj.core.api.Assertions; From 094c976f61cdf9af860bf6e175955144ce9f1c5a Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Fri, 24 May 2024 11:28:39 +1000 Subject: [PATCH 06/26] feat: Add dns discovery classes -- migrated from Tuweni Signed-off-by: Usman Saleem --- ethereum/p2p/build.gradle | 3 - .../ethereum/p2p/discovery/dns/DNSDaemon.java | 125 +++++++++ .../p2p/discovery/dns/DNSDaemonListener.java | 32 +++ .../ethereum/p2p/discovery/dns/DNSEntry.java | 265 ++++++++++++++++++ .../p2p/discovery/dns/DNSResolver.java | 176 ++++++++++++ .../p2p/discovery/dns/DNSVisitor.java | 32 +++ .../ethereum/p2p/discovery/dns/KVReader.java | 49 ++++ .../p2p/network/DefaultP2PNetwork.java | 4 +- gradle/versions.gradle | 4 - 9 files changed, 681 insertions(+), 9 deletions(-) create mode 100644 ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java create mode 100644 ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonListener.java create mode 100644 ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntry.java create mode 100644 ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java create mode 100644 ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSVisitor.java create mode 100644 ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/KVReader.java diff --git a/ethereum/p2p/build.gradle b/ethereum/p2p/build.gradle index a20b947a09d..7a23db58e08 100644 --- a/ethereum/p2p/build.gradle +++ b/ethereum/p2p/build.gradle @@ -50,9 +50,6 @@ dependencies { implementation('io.tmio:tuweni-devp2p') { exclude group:'ch.qos.logback', module:'logback-classic' } - implementation('io.consensys.protocols:besu-dns-discovery'){ - exclude group:'ch.qos.logback', module:'logback-classic' - } implementation 'io.tmio:tuweni-io' implementation 'io.tmio:tuweni-rlp' implementation 'io.tmio:tuweni-units' diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java new file mode 100644 index 00000000000..0238227e48a --- /dev/null +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java @@ -0,0 +1,125 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.p2p.discovery.dns; + +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import io.vertx.core.Handler; +import io.vertx.core.Vertx; +import org.apache.tuweni.devp2p.EthereumNodeRecord; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// Adapted from https://github.com/tmio/tuweni and licensed under Apache 2.0 +// TODO: Deploy DNSDaemon as a worker verticle ?? +/** Resolves DNS records over time, refreshing records. */ +public class DNSDaemon { + private static final Logger LOG = LoggerFactory.getLogger(DNSDaemon.class); + private final String enrLink; + private final long seq; + private final long period; + private final String dnsServer; + private final Vertx vertx; + private long periodicTaskId; + private final Optional listener; + + /** + * Creates a new DNSDaemon. + * + * @param enrLink the ENR link to start with, of the form enrtree://PUBKEY@domain + * @param listener Listener notified when records are read and whenever they are updated. + * @param seq the sequence number of the root record. If the root record seq is higher, proceed + * with visit. + * @param period the period at which to poll DNS records + * @param dnsServer the DNS server to use for DNS query. If null, the default DNS server will be + * used. + * @param vertx Instance of Vertx + */ + public DNSDaemon( + final String enrLink, + final DNSDaemonListener listener, + final long seq, + final long period, + final String dnsServer, + final Vertx vertx) { + this.enrLink = enrLink; + this.listener = Optional.ofNullable(listener); + this.seq = seq; + this.period = period; + this.dnsServer = dnsServer; + this.vertx = vertx; + } + + private void updateRecords(final List records) { + listener.ifPresent(it -> it.newRecords(seq, records)); + } + + /** Starts the DNSDaemon. */ + public void start() { + LOG.trace("Starting DNSDaemon for {}", enrLink); + DNSTimerTask task = new DNSTimerTask(vertx, seq, enrLink, this::updateRecords, dnsServer); + // Use Vertx to run periodic task (TODO: Can we use a worker verticle instead?) + this.periodicTaskId = vertx.setPeriodic(period, task); + } + + /** Stops the DNSDaemon. */ + public void close() { + vertx.cancelTimer(this.periodicTaskId); + } +} + +/** Task that periodically reads DNS records. */ +class DNSTimerTask implements Handler { + private static final Logger LOG = LoggerFactory.getLogger(DNSTimerTask.class); + private final String enrLink; + private final Consumer> records; + private final DNSResolver resolver; + + /** + * Creates a new DNSTimerTask. + * + * @param vertx Instance of Vertx + * @param seq the sequence number of the root record. If the root record seq is higher, proceed + * with visit. + * @param enrLink the ENR link to start with, of the form enrtree://PUBKEY@domain + * @param records Consumer that accepts the records read + * @param dnsServer the DNS server to use for DNS query. If null, the default DNS server will be + * used. + */ + public DNSTimerTask( + final Vertx vertx, + final long seq, + final String enrLink, + final Consumer> records, + final String dnsServer) { + this.enrLink = enrLink; + this.records = records; + resolver = new DNSResolver(dnsServer, seq, vertx); + } + + @Override + public void handle(final Long taskId) { + LOG.debug("Refreshing DNS records for {}", enrLink); + // TODO: Does following need to wrap in executeBlock?? + // measure time taken to call resolver.collectAll + final long startTime = System.nanoTime(); + final List ethereumNodeRecords = resolver.collectAll(enrLink); + final long endTime = System.nanoTime(); + LOG.trace("Time taken to DNSResolver.collectAll: {} ms", (endTime - startTime) / 1_000_000); + records.accept(ethereumNodeRecords); + } +} diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonListener.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonListener.java new file mode 100644 index 00000000000..cfa51d4eb73 --- /dev/null +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonListener.java @@ -0,0 +1,32 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.p2p.discovery.dns; + +import java.util.List; + +import org.apache.tuweni.devp2p.EthereumNodeRecord; + +// Adapted from https://github.com/tmio/tuweni and licensed under Apache 2.0 +/** Callback listening to updates of the DNS records. */ +@FunctionalInterface +public interface DNSDaemonListener { + /** + * Callback called when the seq is updated on the DNS server + * + * @param seq the update identifier of the records + * @param records the records stored on the server + */ + void newRecords(long seq, List records); +} diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntry.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntry.java new file mode 100644 index 00000000000..e48d0068029 --- /dev/null +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntry.java @@ -0,0 +1,265 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.p2p.discovery.dns; + +import static org.hyperledger.besu.ethereum.p2p.discovery.dns.KVReader.readKV; + +import java.net.URI; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.crypto.SECP256K1; +import org.apache.tuweni.devp2p.EthereumNodeRecord; +import org.apache.tuweni.io.Base32; +import org.apache.tuweni.io.Base64URLSafe; +import org.bouncycastle.math.ec.ECPoint; + +// Adapted from https://github.com/tmio/tuweni and licensed under Apache 2.0 +/** Intermediate format to write DNS entries */ +public interface DNSEntry { + + /** + * Read a DNS entry from a String. + * + * @param serialized the serialized form of a DNS entry + * @return DNS entry if found + * @throws IllegalArgumentException if the record cannot be read + */ + static DNSEntry readDNSEntry(final String serialized) { + // trim starting and ending quotes " if it exists in serialized + final String record = serialized.replaceAll("^\"|\"$", ""); + if (record.startsWith("enrtree-root:")) { + return new ENRTreeRoot(readKV(record)); + } else if (record.startsWith("enrtree-branch:")) { + // remove the enrtree-branch: prefix + return new ENRTree(record.substring("enrtree-branch:".length())); + } else if (record.startsWith("enr:")) { + return new ENRNode(readKV(record)); + } else if (record.startsWith("enrtree:")) { + return new ENRTreeLink(record); + } else { + throw new IllegalArgumentException( + serialized + " should contain enrtree-branch, enr, enrtree-root or enrtree"); + } + } + + /** Represents a node in the ENR record. */ + class ENRNode implements DNSEntry { + + private final EthereumNodeRecord nodeRecord; + + /** + * Constructs ENRNode with the given attributes. + * + * @param attrs the attributes of the node + */ + public ENRNode(final Map attrs) { + if (attrs == null) { + throw new IllegalArgumentException("ENRNode attributes cannot be null"); + } + nodeRecord = + Optional.ofNullable(attrs.get("enr")) + .map(Base64URLSafe::decode) + .map(EthereumNodeRecord::fromRLP) + .orElseThrow(() -> new IllegalArgumentException("Invalid ENR record")); + } + + /** + * Ethereum node record. + * + * @return the instance of EthereumNodeRecord + */ + public EthereumNodeRecord nodeRecord() { + return nodeRecord; + } + + @Override + public String toString() { + return nodeRecord.toString(); + } + } + + /** Root of the ENR tree */ + class ENRTreeRoot implements DNSEntry { + private final String version; + private final Long seq; + private final SECP256K1.Signature sig; + private final String enrRoot; + private final String linkRoot; + + /** + * Creates a new ENRTreeRoot + * + * @param attrs The attributes of the root + */ + public ENRTreeRoot(final Map attrs) { + if (attrs == null) { + throw new IllegalArgumentException("ENRNode attributes cannot be null"); + } + + version = + Optional.ofNullable(attrs.get("enrtree-root")) + .orElseThrow(() -> new IllegalArgumentException("Missing attribute enrtree-root")); + seq = + Optional.ofNullable(attrs.get("seq")) + .map(Long::parseLong) + .orElseThrow(() -> new IllegalArgumentException("Missing attribute seq")); + sig = + Optional.ofNullable(attrs.get("sig")) + .map(Base64URLSafe::decode) + .map( + sigBytes -> + SECP256K1.Signature.fromBytes( + Bytes.concatenate( + sigBytes, Bytes.wrap(new byte[Math.max(0, 65 - sigBytes.size())])))) + .orElseThrow(() -> new IllegalArgumentException("Missing attribute sig")); + enrRoot = + Optional.ofNullable(attrs.get("e")) + .orElseThrow(() -> new IllegalArgumentException("Missing attribute e")); + linkRoot = + Optional.ofNullable(attrs.get("l")) + .orElseThrow(() -> new IllegalArgumentException("Missing attribute l")); + } + + /** + * Gets sequence + * + * @return sequence + */ + public Long seq() { + return seq; + } + + /** + * Link root. + * + * @return the link root. + */ + public String linkRoot() { + return linkRoot; + } + + /** + * ENR root. + * + * @return the enr root. + */ + public String enrRoot() { + return enrRoot; + } + + /** + * Signature. + * + * @return SECP256K1 signature + */ + public SECP256K1.Signature sig() { + return sig; + } + + @Override + public String toString() { + return String.format( + "enrtree-root:%s e=%s l=%s seq=%d sig=%s", + version, enrRoot, linkRoot, seq, Base64URLSafe.encode(sig.bytes())); + } + + /** + * Returns the signed content of the root + * + * @return the signed content + */ + public String signedContent() { + return String.format("enrtree-root:%s e=%s l=%s seq=%d", version, enrRoot, linkRoot, seq); + } + } + + /** Represents a branch in the ENR record. */ + class ENRTree implements DNSEntry { + private final List entries; + + /** + * Constructs ENRTree with the given entries. + * + * @param entriesAsString the entries of the branch + */ + public ENRTree(final String entriesAsString) { + entries = + Arrays.stream(entriesAsString.split("[,\"]")) + .filter(it -> it.length() > 4) + .collect(Collectors.toList()); + } + + /** + * Entries of the branch. + * + * @return the entries of the branch + */ + public List entries() { + return entries; + } + + @Override + public String toString() { + return "enrtree-branch:" + String.join(",", entries); + } + } + + /** Class representing an ENR Tree link */ + class ENRTreeLink implements DNSEntry { + private final String domainName; + private final String pubKey; + + /** + * Creates a new ENRTreeLink + * + * @param enrTreeLink The URI representing ENR Tree link + */ + public ENRTreeLink(final String enrTreeLink) { + final URI uri = URI.create(enrTreeLink); + this.domainName = uri.getHost(); + this.pubKey = uri.getUserInfo(); + } + + /** + * Derive public key from the link + * + * @return the public key + */ + public SECP256K1.PublicKey publicKey() { + final byte[] keyBytes = Base32.decodeBytes(pubKey); + final ECPoint ecPoint = SECP256K1.Parameters.CURVE.getCurve().decodePoint(keyBytes); + return SECP256K1.PublicKey.fromBytes(Bytes.wrap(ecPoint.getEncoded(false)).slice(1)); + } + + /** + * Domain name. + * + * @return the domain name + */ + public String domainName() { + return domainName; + } + + @Override + public String toString() { + return String.format("enrtree://%s@%s", pubKey, domainName); + } + } +} diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java new file mode 100644 index 00000000000..027c1637172 --- /dev/null +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java @@ -0,0 +1,176 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.p2p.discovery.dns; + +import org.hyperledger.besu.crypto.Hash; +import org.hyperledger.besu.ethereum.p2p.discovery.dns.DNSEntry.*; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.ExecutionException; + +import io.vertx.core.Vertx; +import io.vertx.core.dns.DnsClient; +import io.vertx.core.dns.DnsClientOptions; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.crypto.SECP256K1; +import org.apache.tuweni.devp2p.EthereumNodeRecord; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// Adapted from https://github.com/tmio/tuweni and licensed under Apache 2.0 +/** Resolves a set of ENR nodes from a host name. */ +public class DNSResolver { + private static final Logger LOG = LoggerFactory.getLogger(DNSResolver.class); + + private long seq; + private final DnsClient dnsClient; + + /** + * Creates a new DNSResolver. + * + * @param dnsServer the DNS server to use for DNS query. If null, the default DNS server will be + * used. + * @param seq the sequence number of the root record. If the root record seq is higher, proceed + * with visit. + * @param vertx Vertx instance. + */ + public DNSResolver(final String dnsServer, long seq, final Vertx vertx) { + this.seq = seq; + final DnsClientOptions dnsClientOptions = new DnsClientOptions(); + if (dnsServer != null) { + dnsClientOptions.setHost(dnsServer); + } + dnsClient = vertx.createDnsClient(dnsClientOptions); + } + + /** + * Convenience method to read all ENRs, from a top-level record. + * + * @param enrLink the ENR link to start with, of the form enrtree://PUBKEY@domain + * @return all ENRs collected + */ + public List collectAll(final String enrLink) { + final List nodes = new ArrayList<>(); // TODO: do we need synchronized list? + final DNSVisitor visitor = nodes::add; + visitTree(new ENRTreeLink(enrLink), visitor); + if (!nodes.isEmpty()) { + LOG.info("Resolved {} nodes from DNS for enr link {}", nodes.size(), enrLink); + } else { + LOG.debug("No nodes resolved from DNS"); + } + return Collections.unmodifiableList(nodes); + } + + /** + * Reads a complete tree of record, starting with the top-level record. + * + * @param link the ENR link to start with + * @param visitor the visitor that will look at each record + */ + private void visitTree(final ENRTreeLink link, final DNSVisitor visitor) { + Optional optionalEntry = resolveRecord(link.domainName()); + if (optionalEntry.isEmpty()) { + LOG.debug("No DNS record found for {}", link.domainName()); + return; + } + + final DNSEntry dnsEntry = optionalEntry.get(); + if (!(dnsEntry instanceof ENRTreeRoot treeRoot)) { + LOG.debug("Root entry {} is not an ENR tree root", dnsEntry); + return; + } + + if (!checkSignature(treeRoot, link.publicKey(), treeRoot.sig())) { + LOG.debug("ENR tree root {} failed signature check", link.domainName()); + return; + } + if (treeRoot.seq() <= seq) { + LOG.debug("ENR tree root seq {} is not higher than {}, aborting", treeRoot.seq(), seq); + return; + } + seq = treeRoot.seq(); + + internalVisit(treeRoot.enrRoot(), link.domainName(), visitor); + internalVisit(treeRoot.linkRoot(), link.domainName(), visitor); + } + + private boolean internalVisit( + final String entryName, final String domainName, final DNSVisitor visitor) { + final Optional optionalDNSEntry = resolveRecord(entryName + "." + domainName); + if (optionalDNSEntry.isEmpty()) { + LOG.debug("No DNS record found for {}", entryName + "." + domainName); + return true; + } + + final DNSEntry entry = optionalDNSEntry.get(); + if (entry instanceof ENRNode node) { + return visitor.visit(node.nodeRecord()); + } else if (entry instanceof ENRTree tree) { + for (String e : tree.entries()) { + boolean keepGoing = internalVisit(e, domainName, visitor); + if (!keepGoing) { + return false; + } + } + } else if (entry instanceof ENRTreeLink link) { + visitTree(link, visitor); + } else { + LOG.debug("Unsupported type of node {}", entry); + } + return true; + } + + /** + * Resolves one DNS record associated with the given domain name. + * + * @param domainName the domain name to query + * @return the DNS entry read from the domain. Empty if no record is found. + */ + Optional resolveRecord(final String domainName) { + return resolveRawRecord(domainName).map(DNSEntry::readDNSEntry); + } + + /** + * Resolves the first TXT record for a domain name and returns it. + * + * @param domainName the name of the DNS domain to query + * @return the first TXT entry of the DNS record. Empty if no record is found. + */ + Optional resolveRawRecord(final String domainName) { + try { + // TODO: is there a better way to do this? + List records = + dnsClient.resolveTXT(domainName).toCompletionStage().toCompletableFuture().get(); + return records.stream().findFirst(); + } catch (InterruptedException | ExecutionException e) { + LOG.debug("Unexpected exception while resolving DNS record for domain {}", domainName, e); + } catch (Exception e) { + LOG.warn("Unexpected exception while resolving DNS record for domain {}", domainName, e); + } + return Optional.empty(); + } + + private boolean checkSignature( + final ENRTreeRoot root, final SECP256K1.PublicKey pubKey, final SECP256K1.Signature sig) { + Bytes32 hash = + Hash.keccak256(Bytes.wrap(root.signedContent().getBytes(StandardCharsets.UTF_8))); + return SECP256K1.verifyHashed(hash, sig, pubKey); + } +} diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSVisitor.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSVisitor.java new file mode 100644 index 00000000000..c6ea0a77ed7 --- /dev/null +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSVisitor.java @@ -0,0 +1,32 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.p2p.discovery.dns; + +import org.apache.tuweni.devp2p.EthereumNodeRecord; + +// Adapted from https://github.com/tmio/tuweni and licensed under Apache 2.0 +/** + * Reads ENR (Ethereum Node Records) entries passed in from DNS. The visitor may decide to stop the + * visit by returning false. + */ +public interface DNSVisitor { + /** + * Visit a new ENR record. + * + * @param enr the ENR record read from DNS + * @return true to continue visiting, false otherwise + */ + boolean visit(final EthereumNodeRecord enr); +} diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/KVReader.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/KVReader.java new file mode 100644 index 00000000000..63123b3ee41 --- /dev/null +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/KVReader.java @@ -0,0 +1,49 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.p2p.discovery.dns; + +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +// Adapted from https://github.com/tmio/tuweni and licensed under Apache 2.0 +/** Read Key value pairs from a DNS record */ +public class KVReader { + private KVReader() {} + + /** + * Read a key value pair from a DNS record + * + * @param record the record to read + * @return the key value pair + */ + public static Map readKV(final String record) { + return Arrays.stream(record.split("\\s+")) + .map( + it -> { + // if it contains an = or :, split into Map.entry from the first occurrence + if (it.contains("=")) { + return it.split("=", 2); + } else if (it.contains(":")) { + return it.split(":", 2); + } else { + // this should not happen, as the record should be well-formed + return new String[] {it}; + } + }) + .filter(kv -> kv.length == 2) + .collect(Collectors.toMap(kv -> kv[0], kv -> kv[1])); + } +} 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 3aae89ec137..18b0a6d1bc4 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 @@ -27,6 +27,8 @@ import org.hyperledger.besu.ethereum.p2p.discovery.PeerDiscoveryAgent; import org.hyperledger.besu.ethereum.p2p.discovery.PeerDiscoveryStatus; import org.hyperledger.besu.ethereum.p2p.discovery.VertxPeerDiscoveryAgent; +import org.hyperledger.besu.ethereum.p2p.discovery.dns.DNSDaemon; +import org.hyperledger.besu.ethereum.p2p.discovery.dns.DNSDaemonListener; import org.hyperledger.besu.ethereum.p2p.discovery.internal.PeerTable; import org.hyperledger.besu.ethereum.p2p.peers.DefaultPeerPrivileges; import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl; @@ -75,8 +77,6 @@ import java.util.stream.Stream; import com.google.common.annotations.VisibleForTesting; -import io.consensys.protocols.util.discovery.DNSDaemon; -import io.consensys.protocols.util.discovery.DNSDaemonListener; import io.vertx.core.Vertx; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.devp2p.EthereumNodeRecord; diff --git a/gradle/versions.gradle b/gradle/versions.gradle index d3938b03330..ff7ce9d6fbf 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -115,10 +115,6 @@ dependencyManagement { entry 'tuweni-units' } - dependencySet(group: 'io.consensys.protocols', version: '1.0.0') { - entry 'besu-dns-discovery' - } - dependencySet(group: 'io.vertx', version: '4.5.4') { entry 'vertx-auth-jwt' entry 'vertx-codegen' From 1704974afeda1c606036a06bc1db3a0e955fe2fb Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Fri, 24 May 2024 15:56:37 +1000 Subject: [PATCH 07/26] test: Add unit tests Signed-off-by: Usman Saleem --- .../ethereum/p2p/discovery/dns/DNSEntry.java | 36 ++++++---- .../p2p/discovery/dns/DNSResolver.java | 8 ++- .../p2p/discovery/dns/DNSEntryTest.java | 71 +++++++++++++++++++ .../p2p/discovery/dns/KVReaderTest.java | 40 +++++++++++ 4 files changed, 138 insertions(+), 17 deletions(-) create mode 100644 ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntryTest.java create mode 100644 ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/KVReaderTest.java diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntry.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntry.java index e48d0068029..5d49f11d2ea 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntry.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntry.java @@ -42,21 +42,29 @@ public interface DNSEntry { * @throws IllegalArgumentException if the record cannot be read */ static DNSEntry readDNSEntry(final String serialized) { - // trim starting and ending quotes " if it exists in serialized - final String record = serialized.replaceAll("^\"|\"$", ""); - if (record.startsWith("enrtree-root:")) { - return new ENRTreeRoot(readKV(record)); - } else if (record.startsWith("enrtree-branch:")) { - // remove the enrtree-branch: prefix - return new ENRTree(record.substring("enrtree-branch:".length())); - } else if (record.startsWith("enr:")) { - return new ENRNode(readKV(record)); - } else if (record.startsWith("enrtree:")) { - return new ENRTreeLink(record); - } else { - throw new IllegalArgumentException( - serialized + " should contain enrtree-branch, enr, enrtree-root or enrtree"); + final String record = trimQuotes(serialized); + final String prefix = getPrefix(record); + return switch (prefix) { + case "enrtree-root" -> new ENRTreeRoot(readKV(record)); + case "enrtree-branch" -> new ENRTree(record.substring(prefix.length() + 1)); + case "enr" -> new ENRNode(readKV(record)); + case "enrtree" -> new ENRTreeLink(record); + default -> + throw new IllegalArgumentException( + serialized + " should contain enrtree-branch, enr, enrtree-root or enrtree"); + }; + } + + private static String trimQuotes(final String str) { + if (str.startsWith("\"") && str.endsWith("\"")) { + return str.substring(1, str.length() - 1); } + return str; + } + + private static String getPrefix(final String input) { + final String[] parts = input.split(":", 2); + return parts.length > 0 ? parts[0] : ""; } /** Represents a node in the ENR record. */ diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java index 027c1637172..d812961f757 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java @@ -15,7 +15,9 @@ package org.hyperledger.besu.ethereum.p2p.discovery.dns; import org.hyperledger.besu.crypto.Hash; -import org.hyperledger.besu.ethereum.p2p.discovery.dns.DNSEntry.*; +import org.hyperledger.besu.ethereum.p2p.discovery.dns.DNSEntry.ENRNode; +import org.hyperledger.besu.ethereum.p2p.discovery.dns.DNSEntry.ENRTreeLink; +import org.hyperledger.besu.ethereum.p2p.discovery.dns.DNSEntry.ENRTreeRoot; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -51,7 +53,7 @@ public class DNSResolver { * with visit. * @param vertx Vertx instance. */ - public DNSResolver(final String dnsServer, long seq, final Vertx vertx) { + public DNSResolver(final String dnsServer, final long seq, final Vertx vertx) { this.seq = seq; final DnsClientOptions dnsClientOptions = new DnsClientOptions(); if (dnsServer != null) { @@ -122,7 +124,7 @@ private boolean internalVisit( final DNSEntry entry = optionalDNSEntry.get(); if (entry instanceof ENRNode node) { return visitor.visit(node.nodeRecord()); - } else if (entry instanceof ENRTree tree) { + } else if (entry instanceof DNSEntry.ENRTree tree) { for (String e : tree.entries()) { boolean keepGoing = internalVisit(e, domainName, visitor); if (!keepGoing) { diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntryTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntryTest.java new file mode 100644 index 00000000000..2197bf279ca --- /dev/null +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntryTest.java @@ -0,0 +1,71 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.p2p.discovery.dns; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.Security; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class DNSEntryTest { + @BeforeAll + static void setup() { + Security.addProvider(new BouncyCastleProvider()); + } + + @Test + void enrTreeRootIsParsed() { + final String txtRecord = + "\"enrtree-root:v1 e=KVKZLGARGADDZSMCF65QQMEWLE l=FDXN3SN67NA5DKA4J2GOK7BVQI seq=919 sig=braPmdwMk-g65lQxums6hEy553s3bWMoecW0QQ0IdykIoM9i3We0bxFT0IDONPaFcRePcN-yaOpt8GBfeQ4qDAE\""; + final DNSEntry entry = DNSEntry.readDNSEntry(txtRecord); + assertThat(entry).isInstanceOf(DNSEntry.ENRTreeRoot.class); + final DNSEntry.ENRTreeRoot enrTreeRoot = (DNSEntry.ENRTreeRoot) entry; + assertThat(enrTreeRoot.enrRoot()).isEqualTo("KVKZLGARGADDZSMCF65QQMEWLE"); + assertThat(enrTreeRoot.linkRoot()).isEqualTo("FDXN3SN67NA5DKA4J2GOK7BVQI"); + assertThat(enrTreeRoot.seq()).isEqualTo(919); + } + + @Test + void enrTreeBranchIsParsed() { + final String txtRecord = + "\"enrtree-branch:HVKDJGU7SZMOAMNLBJYQBSKZTM,PVSVWO3NLKHTBAIWOY2NB67RFI," + + "6TCKCNWXNGBMNFTGSRKNRO4ERA,37NSKCRJVI5XRRHWLTHW4A6OX4,NV3IJMKDVQHHALY6MAVMPYN6ZU," + + "SZCFDMTYOERMIVOUXEWXSGDVEY,FZ26UT4LSG7D2NRX7SV6P3S6BI,7TWNYLCOQ7FEM4IG65WOTL4MVE," + + "6OJXGI7NJUESOLL2OZPS4B\" \"EC6Q,437FN4NSGMGFQLAXYWPX5JNACI,FCA7LN6NCO5IAWPG5FH7LX6XJA," + + "EYBOZ2NZSHDWDSNHV66XASXOHM,FUVRJMMMKJMCL4L4EBEOWCSOFA\""; + final DNSEntry entry = DNSEntry.readDNSEntry(txtRecord); + + assertThat(entry).isInstanceOf(DNSEntry.ENRTree.class); + assertThat(((DNSEntry.ENRTree) entry).entries()) + .containsExactly( + "HVKDJGU7SZMOAMNLBJYQBSKZTM", + "PVSVWO3NLKHTBAIWOY2NB67RFI", + "6TCKCNWXNGBMNFTGSRKNRO4ERA", + "37NSKCRJVI5XRRHWLTHW4A6OX4", + "NV3IJMKDVQHHALY6MAVMPYN6ZU", + "SZCFDMTYOERMIVOUXEWXSGDVEY", + "FZ26UT4LSG7D2NRX7SV6P3S6BI", + "7TWNYLCOQ7FEM4IG65WOTL4MVE", + "6OJXGI7NJUESOLL2OZPS4B", + "437FN4NSGMGFQLAXYWPX5JNACI", + "FCA7LN6NCO5IAWPG5FH7LX6XJA", + "EYBOZ2NZSHDWDSNHV66XASXOHM", + "FUVRJMMMKJMCL4L4EBEOWCSOFA") + .doesNotContain("EC6Q"); + } +} diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/KVReaderTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/KVReaderTest.java new file mode 100644 index 00000000000..2f1ecaf293c --- /dev/null +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/KVReaderTest.java @@ -0,0 +1,40 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.p2p.discovery.dns; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +public class KVReaderTest { + // copied from `dig all.holesky.ethdisco.net txt` + private static final String txtRecord = + "enrtree-root:v1 e=KVKZLGARGADDZSMCF65QQMEWLE l=FDXN3SN67NA5DKA4J2GOK7BVQI seq=919 sig=braPmdwMk-g65lQxums6hEy553s3bWMoecW0QQ0IdykIoM9i3We0bxFT0IDONPaFcRePcN-yaOpt8GBfeQ4qDAE"; + + @Test + void parseTXTRecord() { + final Map kv = KVReader.readKV(txtRecord); + assertThat(kv) + .containsEntry("enrtree-root", "v1") + .containsEntry("e", "KVKZLGARGADDZSMCF65QQMEWLE") + .containsEntry("l", "FDXN3SN67NA5DKA4J2GOK7BVQI") + .containsEntry("seq", "919") + .containsEntry( + "sig", + "braPmdwMk-g65lQxums6hEy553s3bWMoecW0QQ0IdykIoM9i3We0bxFT0IDONPaFcRePcN-yaOpt8GBfeQ4qDAE"); + } +} From 486c61c7d1e2fe1450175d7f1bed845e993fe422 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Sun, 26 May 2024 12:23:19 +1000 Subject: [PATCH 08/26] feat: Use a separate thread to convert vertx dns-client call to sync Signed-off-by: Usman Saleem --- ethereum/p2p/build.gradle | 2 + .../ethereum/p2p/discovery/dns/DNSDaemon.java | 27 ++++--- .../p2p/discovery/dns/DNSResolver.java | 48 +++++++++--- .../p2p/network/DefaultP2PNetwork.java | 15 +++- .../p2p/discovery/dns/DNSDaemonTest.java | 73 +++++++++++++++++++ 5 files changed, 139 insertions(+), 26 deletions(-) create mode 100644 ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java diff --git a/ethereum/p2p/build.gradle b/ethereum/p2p/build.gradle index 7a23db58e08..a5627cb13f7 100644 --- a/ethereum/p2p/build.gradle +++ b/ethereum/p2p/build.gradle @@ -81,9 +81,11 @@ dependencies { } testImplementation 'io.vertx:vertx-codegen' testImplementation 'io.vertx:vertx-unit' + testImplementation 'io.vertx:vertx-junit5' testImplementation 'org.assertj:assertj-core' testImplementation 'org.awaitility:awaitility' testImplementation 'org.junit.jupiter:junit-jupiter' testImplementation 'org.mockito:mockito-core' testImplementation 'org.mockito:mockito-junit-jupiter' + } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java index 0238227e48a..6e5f782a804 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java @@ -18,6 +18,7 @@ import java.util.Optional; import java.util.function.Consumer; +import io.vertx.core.AbstractVerticle; import io.vertx.core.Handler; import io.vertx.core.Vertx; import org.apache.tuweni.devp2p.EthereumNodeRecord; @@ -27,13 +28,12 @@ // Adapted from https://github.com/tmio/tuweni and licensed under Apache 2.0 // TODO: Deploy DNSDaemon as a worker verticle ?? /** Resolves DNS records over time, refreshing records. */ -public class DNSDaemon { +public class DNSDaemon extends AbstractVerticle { private static final Logger LOG = LoggerFactory.getLogger(DNSDaemon.class); private final String enrLink; private final long seq; private final long period; private final String dnsServer; - private final Vertx vertx; private long periodicTaskId; private final Optional listener; @@ -47,21 +47,18 @@ public class DNSDaemon { * @param period the period at which to poll DNS records * @param dnsServer the DNS server to use for DNS query. If null, the default DNS server will be * used. - * @param vertx Instance of Vertx */ public DNSDaemon( final String enrLink, final DNSDaemonListener listener, final long seq, final long period, - final String dnsServer, - final Vertx vertx) { + final String dnsServer) { this.enrLink = enrLink; this.listener = Optional.ofNullable(listener); this.seq = seq; this.period = period; this.dnsServer = dnsServer; - this.vertx = vertx; } private void updateRecords(final List records) { @@ -69,16 +66,24 @@ private void updateRecords(final List records) { } /** Starts the DNSDaemon. */ + @Override public void start() { - LOG.trace("Starting DNSDaemon for {}", enrLink); + LOG.info("Starting DNSDaemon for {}", enrLink); DNSTimerTask task = new DNSTimerTask(vertx, seq, enrLink, this::updateRecords, dnsServer); // Use Vertx to run periodic task (TODO: Can we use a worker verticle instead?) - this.periodicTaskId = vertx.setPeriodic(period, task); + if (period > 0) { + this.periodicTaskId = vertx.setPeriodic(period, task); + } else { + task.handle(0L); + } } /** Stops the DNSDaemon. */ - public void close() { - vertx.cancelTimer(this.periodicTaskId); + @Override + public void stop() { + if (period > 0) { + vertx.cancelTimer(this.periodicTaskId); + } // otherwise we didn't start the timer } } @@ -119,7 +124,7 @@ public void handle(final Long taskId) { final long startTime = System.nanoTime(); final List ethereumNodeRecords = resolver.collectAll(enrLink); final long endTime = System.nanoTime(); - LOG.trace("Time taken to DNSResolver.collectAll: {} ms", (endTime - startTime) / 1_000_000); + LOG.info("Time taken to DNSResolver.collectAll: {} ms", (endTime - startTime) / 1_000_000); records.accept(ethereumNodeRecords); } } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java index d812961f757..00a0e749249 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java @@ -24,7 +24,10 @@ import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.concurrent.ExecutionException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicReference; import io.vertx.core.Vertx; import io.vertx.core.dns.DnsClient; @@ -40,7 +43,7 @@ /** Resolves a set of ENR nodes from a host name. */ public class DNSResolver { private static final Logger LOG = LoggerFactory.getLogger(DNSResolver.class); - + private final ExecutorService rawTxtRecordsExecutor = Executors.newSingleThreadExecutor(); private long seq; private final DnsClient dnsClient; @@ -156,17 +159,40 @@ Optional resolveRecord(final String domainName) { * @return the first TXT entry of the DNS record. Empty if no record is found. */ Optional resolveRawRecord(final String domainName) { + // vertx-dns is async, kotlin coroutines allows us to await, similarly Java 21 new thread + // model would also allow us to await. For now, we will use CountDownLatch to block the + // current thread until the DNS resolution is complete. + LOG.info("Resolving TXT records on domain: {}", domainName); + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference> record = new AtomicReference<>(Optional.empty()); + rawTxtRecordsExecutor.submit( + () -> { + dnsClient + .resolveTXT(domainName) + .onComplete( + ar -> { + if (ar.succeeded()) { + LOG.info( + "TXT record resolved on domain {}. Result: {}", domainName, ar.result()); + record.set(ar.result().stream().findFirst()); + } else { + LOG.warn( + "TXT record not resolved on domain {}, because: {}", + domainName, + ar.cause().getMessage()); + } + latch.countDown(); + }); + }); + try { - // TODO: is there a better way to do this? - List records = - dnsClient.resolveTXT(domainName).toCompletionStage().toCompletableFuture().get(); - return records.stream().findFirst(); - } catch (InterruptedException | ExecutionException e) { - LOG.debug("Unexpected exception while resolving DNS record for domain {}", domainName, e); - } catch (Exception e) { - LOG.warn("Unexpected exception while resolving DNS record for domain {}", domainName, e); + // causes the worker thread to wait. Once we move to Java 21, this can be simplified. + latch.await(); + } catch (InterruptedException e) { + LOG.debug("Interrupted while waiting for DNS resolution"); } - return Optional.empty(); + + return record.get(); } private boolean checkSignature( 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 18b0a6d1bc4..f9681726478 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 @@ -77,6 +77,8 @@ import java.util.stream.Stream; import com.google.common.annotations.VisibleForTesting; +import io.vertx.core.DeploymentOptions; +import io.vertx.core.ThreadingModel; import io.vertx.core.Vertx; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.devp2p.EthereumNodeRecord; @@ -233,9 +235,13 @@ public void start() { createDaemonListener(), 0L, 600000L, - config.getDnsDiscoveryServerOverride().orElse(null), - vertx); - dnsDaemon.start(); + config.getDnsDiscoveryServerOverride().orElse(null)); + // dnsDaemon.start(); + final DeploymentOptions options = + new DeploymentOptions() + .setThreadingModel(ThreadingModel.WORKER) + .setWorkerPoolSize(1); + vertx.deployVerticle(dnsDaemon, options); }); final int listeningPort = rlpxAgent.start().join(); @@ -282,7 +288,8 @@ public void stop() { return; } - getDnsDaemon().ifPresent(DNSDaemon::close); + // this will close the timer, but won't undeploy the vertical. vertx.stop should do that. + getDnsDaemon().ifPresent(DNSDaemon::stop); peerConnectionScheduler.shutdownNow(); peerDiscoveryAgent.stop().whenComplete((res, err) -> shutdownLatch.countDown()); diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java new file mode 100644 index 00000000000..0be9f00d813 --- /dev/null +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java @@ -0,0 +1,73 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.p2p.discovery.dns; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.Security; +import java.util.concurrent.TimeUnit; + +import io.vertx.core.DeploymentOptions; +import io.vertx.core.ThreadingModel; +import io.vertx.core.Vertx; +import io.vertx.junit5.VertxExtension; +import io.vertx.junit5.VertxTestContext; +import org.apache.tuweni.devp2p.EthereumNodeRecord; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(VertxExtension.class) +class DNSDaemonTest { + private static final String holeskyEnr = + "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@all.holesky.ethdisco.net"; + private DNSDaemon dnsDaemon; + + @BeforeAll + static void setup() { + Security.addProvider(new BouncyCastleProvider()); + } + + @AfterEach + void tearDown() {} + + @Test + void testDNSDaemon(final Vertx vertx, final VertxTestContext testContext) + throws InterruptedException { + dnsDaemon = + new DNSDaemon( + holeskyEnr, + (seq, records) -> { + System.out.println("seq: " + seq); + for (EthereumNodeRecord record : records) { + System.out.println(record); + } + if (!records.isEmpty()) { + testContext.completeNow(); + } + }, + 0, + 0, + null); + + DeploymentOptions options = + new DeploymentOptions().setThreadingModel(ThreadingModel.WORKER).setWorkerPoolSize(1); + vertx.deployVerticle(dnsDaemon, options); + + assertThat(testContext.awaitCompletion(30, TimeUnit.SECONDS)).isTrue(); + } +} From 31a121bdb8a83a2a5d7b5663eaecdff46b5bf1e2 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Sun, 26 May 2024 19:16:30 +1000 Subject: [PATCH 09/26] codefix: spotless apply Signed-off-by: Usman Saleem --- .../ethereum/p2p/discovery/dns/DNSDaemon.java | 68 +++++++------------ .../p2p/discovery/dns/DNSResolver.java | 15 ++-- .../p2p/discovery/dns/DNSDaemonTest.java | 2 +- 3 files changed, 33 insertions(+), 52 deletions(-) diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java index 6e5f782a804..587b3436c7f 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java @@ -16,18 +16,17 @@ import java.util.List; import java.util.Optional; -import java.util.function.Consumer; import io.vertx.core.AbstractVerticle; -import io.vertx.core.Handler; -import io.vertx.core.Vertx; import org.apache.tuweni.devp2p.EthereumNodeRecord; import org.slf4j.Logger; import org.slf4j.LoggerFactory; // Adapted from https://github.com/tmio/tuweni and licensed under Apache 2.0 -// TODO: Deploy DNSDaemon as a worker verticle ?? -/** Resolves DNS records over time, refreshing records. */ +/** + * Resolves DNS records over time, refreshing records. This is written as a Vertx Verticle which + * allows to run outside the Vertx event loop + */ public class DNSDaemon extends AbstractVerticle { private static final Logger LOG = LoggerFactory.getLogger(DNSDaemon.class); private final String enrLink; @@ -36,6 +35,7 @@ public class DNSDaemon extends AbstractVerticle { private final String dnsServer; private long periodicTaskId; private final Optional listener; + private final DNSResolver dnsResolver; /** * Creates a new DNSDaemon. @@ -44,7 +44,7 @@ public class DNSDaemon extends AbstractVerticle { * @param listener Listener notified when records are read and whenever they are updated. * @param seq the sequence number of the root record. If the root record seq is higher, proceed * with visit. - * @param period the period at which to poll DNS records + * @param period the period at which to poll DNS records. If negative or zero, it runs only once. * @param dnsServer the DNS server to use for DNS query. If null, the default DNS server will be * used. */ @@ -59,8 +59,14 @@ public DNSDaemon( this.seq = seq; this.period = period; this.dnsServer = dnsServer; + dnsResolver = new DNSResolver(vertx, enrLink, seq, dnsServer); } + /** + * Callback method to update the listeners with resolved enr records. + * + * @param records List of resolved Ethereum Node Records. + */ private void updateRecords(final List records) { listener.ifPresent(it -> it.newRecords(seq, records)); } @@ -69,12 +75,11 @@ private void updateRecords(final List records) { @Override public void start() { LOG.info("Starting DNSDaemon for {}", enrLink); - DNSTimerTask task = new DNSTimerTask(vertx, seq, enrLink, this::updateRecords, dnsServer); - // Use Vertx to run periodic task (TODO: Can we use a worker verticle instead?) + // Use Vertx to run periodic task if period is set if (period > 0) { - this.periodicTaskId = vertx.setPeriodic(period, task); + this.periodicTaskId = vertx.setPeriodic(period, this::refreshENRRecords); } else { - task.handle(0L); + refreshENRRecords(0L); } } @@ -84,47 +89,20 @@ public void stop() { if (period > 0) { vertx.cancelTimer(this.periodicTaskId); } // otherwise we didn't start the timer + // TODO: Call dnsResolver stop } -} - -/** Task that periodically reads DNS records. */ -class DNSTimerTask implements Handler { - private static final Logger LOG = LoggerFactory.getLogger(DNSTimerTask.class); - private final String enrLink; - private final Consumer> records; - private final DNSResolver resolver; /** - * Creates a new DNSTimerTask. + * Refresh enr records by calling dnsResolver and updating the listeners. * - * @param vertx Instance of Vertx - * @param seq the sequence number of the root record. If the root record seq is higher, proceed - * with visit. - * @param enrLink the ENR link to start with, of the form enrtree://PUBKEY@domain - * @param records Consumer that accepts the records read - * @param dnsServer the DNS server to use for DNS query. If null, the default DNS server will be - * used. + * @param taskId the task id of the periodic task */ - public DNSTimerTask( - final Vertx vertx, - final long seq, - final String enrLink, - final Consumer> records, - final String dnsServer) { - this.enrLink = enrLink; - this.records = records; - resolver = new DNSResolver(dnsServer, seq, vertx); - } - - @Override - public void handle(final Long taskId) { - LOG.debug("Refreshing DNS records for {}", enrLink); - // TODO: Does following need to wrap in executeBlock?? - // measure time taken to call resolver.collectAll + void refreshENRRecords(final Long taskId) { + LOG.debug("Refreshing DNS records"); final long startTime = System.nanoTime(); - final List ethereumNodeRecords = resolver.collectAll(enrLink); + final List ethereumNodeRecords = dnsResolver.collectAll(); final long endTime = System.nanoTime(); - LOG.info("Time taken to DNSResolver.collectAll: {} ms", (endTime - startTime) / 1_000_000); - records.accept(ethereumNodeRecords); + LOG.debug("Time taken to DNSResolver.collectAll: {} ms", (endTime - startTime) / 1_000_000); + updateRecords(ethereumNodeRecords); } } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java index 00a0e749249..b9946cdcf44 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java @@ -44,19 +44,23 @@ public class DNSResolver { private static final Logger LOG = LoggerFactory.getLogger(DNSResolver.class); private final ExecutorService rawTxtRecordsExecutor = Executors.newSingleThreadExecutor(); + private final String enrLink; private long seq; private final DnsClient dnsClient; /** * Creates a new DNSResolver. * - * @param dnsServer the DNS server to use for DNS query. If null, the default DNS server will be - * used. + * @param vertx Vertx instance which is used to create DNS Client + * @param enrLink the ENR link to start with, of the form enrtree://PUBKEY@domain * @param seq the sequence number of the root record. If the root record seq is higher, proceed * with visit. - * @param vertx Vertx instance. + * @param dnsServer the DNS server to use for DNS query. If null, the default DNS server will be + * used. */ - public DNSResolver(final String dnsServer, final long seq, final Vertx vertx) { + public DNSResolver( + final Vertx vertx, final String enrLink, final long seq, final String dnsServer) { + this.enrLink = enrLink; this.seq = seq; final DnsClientOptions dnsClientOptions = new DnsClientOptions(); if (dnsServer != null) { @@ -68,10 +72,9 @@ public DNSResolver(final String dnsServer, final long seq, final Vertx vertx) { /** * Convenience method to read all ENRs, from a top-level record. * - * @param enrLink the ENR link to start with, of the form enrtree://PUBKEY@domain * @return all ENRs collected */ - public List collectAll(final String enrLink) { + public List collectAll() { final List nodes = new ArrayList<>(); // TODO: do we need synchronized list? final DNSVisitor visitor = nodes::add; visitTree(new ENRTreeLink(enrLink), visitor); diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java index 0be9f00d813..3d4ed8f2bdb 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java @@ -57,7 +57,7 @@ void testDNSDaemon(final Vertx vertx, final VertxTestContext testContext) System.out.println(record); } if (!records.isEmpty()) { - testContext.completeNow(); + testContext.completeNow(); } }, 0, From 45d8faaac08760795bae126c5473d4c96ba08aef Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Mon, 27 May 2024 09:14:32 +1000 Subject: [PATCH 10/26] fix unit test and refactor periodic timer task Signed-off-by: Usman Saleem --- .../ethereum/p2p/discovery/dns/DNSDaemon.java | 32 +++++++------------ .../p2p/discovery/dns/DNSResolver.java | 15 +++++---- .../p2p/network/DefaultP2PNetwork.java | 25 +++++++++++---- .../p2p/network/DefaultP2PNetworkTest.java | 29 ++++++++++------- 4 files changed, 55 insertions(+), 46 deletions(-) diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java index 587b3436c7f..5e3c46fa667 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java @@ -32,10 +32,10 @@ public class DNSDaemon extends AbstractVerticle { private final String enrLink; private final long seq; private final long period; - private final String dnsServer; - private long periodicTaskId; private final Optional listener; - private final DNSResolver dnsResolver; + private final Optional dnsServer; + private Optional periodicTaskId = Optional.empty(); + private DNSResolver dnsResolver; /** * Creates a new DNSDaemon. @@ -58,27 +58,18 @@ public DNSDaemon( this.listener = Optional.ofNullable(listener); this.seq = seq; this.period = period; - this.dnsServer = dnsServer; - dnsResolver = new DNSResolver(vertx, enrLink, seq, dnsServer); - } - - /** - * Callback method to update the listeners with resolved enr records. - * - * @param records List of resolved Ethereum Node Records. - */ - private void updateRecords(final List records) { - listener.ifPresent(it -> it.newRecords(seq, records)); + this.dnsServer = Optional.ofNullable(dnsServer); } /** Starts the DNSDaemon. */ @Override public void start() { LOG.info("Starting DNSDaemon for {}", enrLink); - // Use Vertx to run periodic task if period is set + this.dnsResolver = new DNSResolver(vertx, enrLink, seq, dnsServer); if (period > 0) { - this.periodicTaskId = vertx.setPeriodic(period, this::refreshENRRecords); + periodicTaskId = Optional.of(vertx.setPeriodic(period, this::refreshENRRecords)); } else { + // do one-shot resolution refreshENRRecords(0L); } } @@ -86,10 +77,9 @@ public void start() { /** Stops the DNSDaemon. */ @Override public void stop() { - if (period > 0) { - vertx.cancelTimer(this.periodicTaskId); - } // otherwise we didn't start the timer - // TODO: Call dnsResolver stop + LOG.info("Stopping DNSDaemon for {}", enrLink); + periodicTaskId.ifPresent(vertx::cancelTimer); + dnsResolver.close(); } /** @@ -103,6 +93,6 @@ void refreshENRRecords(final Long taskId) { final List ethereumNodeRecords = dnsResolver.collectAll(); final long endTime = System.nanoTime(); LOG.debug("Time taken to DNSResolver.collectAll: {} ms", (endTime - startTime) / 1_000_000); - updateRecords(ethereumNodeRecords); + listener.ifPresent(it -> it.newRecords(seq, ethereumNodeRecords)); } } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java index b9946cdcf44..1791389d593 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java @@ -41,7 +41,7 @@ // Adapted from https://github.com/tmio/tuweni and licensed under Apache 2.0 /** Resolves a set of ENR nodes from a host name. */ -public class DNSResolver { +public class DNSResolver implements AutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(DNSResolver.class); private final ExecutorService rawTxtRecordsExecutor = Executors.newSingleThreadExecutor(); private final String enrLink; @@ -55,17 +55,15 @@ public class DNSResolver { * @param enrLink the ENR link to start with, of the form enrtree://PUBKEY@domain * @param seq the sequence number of the root record. If the root record seq is higher, proceed * with visit. - * @param dnsServer the DNS server to use for DNS query. If null, the default DNS server will be + * @param dnsServer the DNS server to use for DNS query. If empty, the default DNS server will be * used. */ public DNSResolver( - final Vertx vertx, final String enrLink, final long seq, final String dnsServer) { + final Vertx vertx, final String enrLink, final long seq, final Optional dnsServer) { this.enrLink = enrLink; this.seq = seq; final DnsClientOptions dnsClientOptions = new DnsClientOptions(); - if (dnsServer != null) { - dnsClientOptions.setHost(dnsServer); - } + dnsServer.ifPresent(dnsClientOptions::setHost); dnsClient = vertx.createDnsClient(dnsClientOptions); } @@ -204,4 +202,9 @@ private boolean checkSignature( Hash.keccak256(Bytes.wrap(root.signedContent().getBytes(StandardCharsets.UTF_8))); return SECP256K1.verifyHashed(hash, sig, pubKey); } + + @Override + public void close() { + rawTxtRecordsExecutor.shutdown(); + } } 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 f9681726478..f6a583996d6 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 @@ -71,6 +71,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.function.Consumer; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -78,8 +79,10 @@ import com.google.common.annotations.VisibleForTesting; import io.vertx.core.DeploymentOptions; +import io.vertx.core.Future; import io.vertx.core.ThreadingModel; import io.vertx.core.Vertx; +import org.apache.commons.lang3.tuple.Pair; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.devp2p.EthereumNodeRecord; import org.slf4j.Logger; @@ -149,7 +152,8 @@ public class DefaultP2PNetwork implements P2PNetwork { private final CountDownLatch shutdownLatch = new CountDownLatch(2); private final Duration shutdownTimeout = Duration.ofSeconds(15); private final Vertx vertx; - private DNSDaemon dnsDaemon; + private final AtomicReference>> dnsDaemonRef = + new AtomicReference<>(Optional.empty()); /** * Creates a peer networking service for production purposes. @@ -229,19 +233,25 @@ public void start() { LOG.info( "Starting DNS discovery with DNS Server override {}", dnsServer)); - dnsDaemon = + final DNSDaemon dnsDaemon = new DNSDaemon( disco, createDaemonListener(), 0L, 600000L, config.getDnsDiscoveryServerOverride().orElse(null)); - // dnsDaemon.start(); + + // TODO: Java 21, we can move to Virtual Thread model final DeploymentOptions options = new DeploymentOptions() .setThreadingModel(ThreadingModel.WORKER) + .setInstances(1) .setWorkerPoolSize(1); - vertx.deployVerticle(dnsDaemon, options); + + final Future deployId = vertx.deployVerticle(dnsDaemon, options); + final String dnsDaemonDeployId = + deployId.toCompletionStage().toCompletableFuture().join(); + dnsDaemonRef.set(Optional.of(Pair.of(dnsDaemonDeployId, dnsDaemon))); }); final int listeningPort = rlpxAgent.start().join(); @@ -288,8 +298,9 @@ public void stop() { return; } - // this will close the timer, but won't undeploy the vertical. vertx.stop should do that. - getDnsDaemon().ifPresent(DNSDaemon::stop); + // since dnsDaemon is a vertx verticle, vertx.close will undeploy it. + // However, we can safely call stop as well. + dnsDaemonRef.get().map(Pair::getRight).ifPresent(DNSDaemon::stop); peerConnectionScheduler.shutdownNow(); peerDiscoveryAgent.stop().whenComplete((res, err) -> shutdownLatch.countDown()); @@ -346,7 +357,7 @@ public boolean removeMaintainedConnectionPeer(final Peer peer) { @VisibleForTesting Optional getDnsDaemon() { - return Optional.ofNullable(dnsDaemon); + return dnsDaemonRef.get().map(Pair::getRight); } @VisibleForTesting diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetworkTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetworkTest.java index c468996a14b..88b9197262a 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetworkTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetworkTest.java @@ -58,8 +58,6 @@ import java.util.stream.Stream; import io.vertx.core.Vertx; -import io.vertx.core.dns.DnsClient; -import io.vertx.core.impl.ContextInternal; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.crypto.SECP256K1; import org.assertj.core.api.Assertions; @@ -335,16 +333,21 @@ public void shouldStartDnsDiscoveryWhenDnsURLIsConfigured() { final NetworkingConfiguration dnsConfig = when(spy(config).getDiscovery()).thenReturn(disco).getMock(); - Vertx vertx = mock(Vertx.class); - when(vertx.createDnsClient(any())).thenReturn(mock(DnsClient.class)); - when(vertx.getOrCreateContext()).thenReturn(mock(ContextInternal.class)); + final Vertx vertx = Vertx.vertx(); // use real instance // spy on DefaultP2PNetwork final DefaultP2PNetwork testClass = (DefaultP2PNetwork) builder().vertx(vertx).config(dnsConfig).build(); testClass.start(); - assertThat(testClass.getDnsDaemon()).isPresent(); + try { + // the actual lookup won't work because of mock discovery url, however, a valid DNSDaemon + // should be created. + assertThat(testClass.getDnsDaemon()).isPresent(); + } finally { + testClass.stop(); + vertx.close(); + } } @Test @@ -358,17 +361,19 @@ public void shouldUseDnsServerOverrideIfPresent() { doReturn(disco).when(dnsConfig).getDiscovery(); doReturn(Optional.of("localhost")).when(dnsConfig).getDnsDiscoveryServerOverride(); - Vertx vertx = mock(Vertx.class); - when(vertx.createDnsClient(any())).thenReturn(mock(DnsClient.class)); - when(vertx.getOrCreateContext()).thenReturn(mock(ContextInternal.class)); - + Vertx vertx = Vertx.vertx(); // use real instance final DefaultP2PNetwork testClass = (DefaultP2PNetwork) builder().config(dnsConfig).vertx(vertx).build(); testClass.start(); // ensure we used the dns server override config when building DNSDaemon: - assertThat(testClass.getDnsDaemon()).isPresent(); - verify(dnsConfig, times(2)).getDnsDiscoveryServerOverride(); + try { + assertThat(testClass.getDnsDaemon()).isPresent(); + verify(dnsConfig, times(2)).getDnsDiscoveryServerOverride(); + } finally { + testClass.stop(); + vertx.close(); + } } private DefaultP2PNetwork network() { From 00c519da5276d72f3775863e4617beec0a8f0234 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Mon, 27 May 2024 09:18:21 +1000 Subject: [PATCH 11/26] changelog: Update changelog Signed-off-by: Usman Saleem --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42856d56642..58bcfa4546d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ - Default bonsai to use full-flat db and code-storage-by-code-hash [#6984](https://github.com/hyperledger/besu/pull/6894) - New RPC methods miner_setExtraData and miner_getExtraData [#7078](https://github.com/hyperledger/besu/pull/7078) - Disconnect peers that have multiple discovery ports since they give us bad neighbours [#7089](https://github.com/hyperledger/besu/pull/7089) -- Use Consensys [besu-dns-discovery](https://github.com/Consensys/besu-dns-discovery) module which is a fork of Tuweni DNS discovery with performance related enhancements .[#7129](https://github.com/hyperledger/besu/pull/7129) +- Port Tuweni DNS discovery into Besu p22 module. [#7129](https://github.com/hyperledger/besu/pull/7129) ### Bug fixes - Fix parsing `gasLimit` parameter when its value is > `Long.MAX_VALUE` [#7116](https://github.com/hyperledger/besu/pull/7116) From 2193921c85bcd9b11b4fdf704d236a13d98d47a7 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Tue, 28 May 2024 08:53:52 +1000 Subject: [PATCH 12/26] refactor and cleaning up Signed-off-by: Usman Saleem --- .../ethereum/p2p/discovery/dns/DNSDaemon.java | 21 +- .../p2p/discovery/dns/DNSResolver.java | 41 +- .../p2p/network/DefaultP2PNetwork.java | 1 + .../p2p/discovery/dns/DNSDaemonTest.java | 78 ++- .../p2p/discovery/dns/DnsPortSplitTest.java | 38 ++ .../p2p/discovery/dns/MockDNSServer.java | 632 ++++++++++++++++++ .../p2p/src/test/resources/log4j2-test.xml | 19 + 7 files changed, 800 insertions(+), 30 deletions(-) create mode 100644 ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DnsPortSplitTest.java create mode 100644 ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/MockDNSServer.java create mode 100644 ethereum/p2p/src/test/resources/log4j2-test.xml diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java index 5e3c46fa667..18cfe5b7879 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java @@ -31,7 +31,8 @@ public class DNSDaemon extends AbstractVerticle { private static final Logger LOG = LoggerFactory.getLogger(DNSDaemon.class); private final String enrLink; private final long seq; - private final long period; + private final long initialDelay; + private final long delay; private final Optional listener; private final Optional dnsServer; private Optional periodicTaskId = Optional.empty(); @@ -44,7 +45,9 @@ public class DNSDaemon extends AbstractVerticle { * @param listener Listener notified when records are read and whenever they are updated. * @param seq the sequence number of the root record. If the root record seq is higher, proceed * with visit. - * @param period the period at which to poll DNS records. If negative or zero, it runs only once. + * @param initialDelay the delay in milliseconds before the first poll of DNS records. + * @param delay the delay in milliseconds at which to poll DNS records. If negative or zero, it + * runs only once. * @param dnsServer the DNS server to use for DNS query. If null, the default DNS server will be * used. */ @@ -52,22 +55,24 @@ public DNSDaemon( final String enrLink, final DNSDaemonListener listener, final long seq, - final long period, + final long initialDelay, + final long delay, final String dnsServer) { this.enrLink = enrLink; this.listener = Optional.ofNullable(listener); this.seq = seq; - this.period = period; + this.initialDelay = initialDelay; + this.delay = delay; this.dnsServer = Optional.ofNullable(dnsServer); } /** Starts the DNSDaemon. */ @Override public void start() { - LOG.info("Starting DNSDaemon for {}", enrLink); + LOG.info("Starting DNSDaemon for {}, using {} DNS host", enrLink, dnsServer.orElse("default")); this.dnsResolver = new DNSResolver(vertx, enrLink, seq, dnsServer); - if (period > 0) { - periodicTaskId = Optional.of(vertx.setPeriodic(period, this::refreshENRRecords)); + if (delay > 0) { + periodicTaskId = Optional.of(vertx.setPeriodic(initialDelay, delay, this::refreshENRRecords)); } else { // do one-shot resolution refreshENRRecords(0L); @@ -93,6 +98,6 @@ void refreshENRRecords(final Long taskId) { final List ethereumNodeRecords = dnsResolver.collectAll(); final long endTime = System.nanoTime(); LOG.debug("Time taken to DNSResolver.collectAll: {} ms", (endTime - startTime) / 1_000_000); - listener.ifPresent(it -> it.newRecords(seq, ethereumNodeRecords)); + listener.ifPresent(it -> it.newRecords(dnsResolver.sequence(), ethereumNodeRecords)); } } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java index 1791389d593..ee39a0a0289 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java @@ -29,6 +29,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicReference; +import com.google.common.base.Splitter; import io.vertx.core.Vertx; import io.vertx.core.dns.DnsClient; import io.vertx.core.dns.DnsClientOptions; @@ -62,28 +63,52 @@ public DNSResolver( final Vertx vertx, final String enrLink, final long seq, final Optional dnsServer) { this.enrLink = enrLink; this.seq = seq; - final DnsClientOptions dnsClientOptions = new DnsClientOptions(); - dnsServer.ifPresent(dnsClientOptions::setHost); + final DnsClientOptions dnsClientOptions = + dnsServer.map(DNSResolver::buildDnsClientOptions).orElseGet(DnsClientOptions::new); dnsClient = vertx.createDnsClient(dnsClientOptions); } + private static DnsClientOptions buildDnsClientOptions(String server) { + final List hostPort = Splitter.on(":").splitToList(server); + final DnsClientOptions dnsClientOptions = new DnsClientOptions(); + dnsClientOptions.setHost(hostPort.get(0)); + if (hostPort.size() > 1) { + try { + int port = Integer.parseInt(hostPort.get(1)); + dnsClientOptions.setPort(port); + } catch (NumberFormatException e) { + LOG.trace("Invalid port number {}, ignoring", hostPort.get(1)); + } + } + return dnsClientOptions; + } + /** * Convenience method to read all ENRs, from a top-level record. * * @return all ENRs collected */ public List collectAll() { - final List nodes = new ArrayList<>(); // TODO: do we need synchronized list? + final List nodes = new ArrayList<>(); final DNSVisitor visitor = nodes::add; visitTree(new ENRTreeLink(enrLink), visitor); if (!nodes.isEmpty()) { - LOG.info("Resolved {} nodes from DNS for enr link {}", nodes.size(), enrLink); + LOG.debug("Resolved {} nodes from DNS for enr link {}", nodes.size(), enrLink); } else { LOG.debug("No nodes resolved from DNS"); } return Collections.unmodifiableList(nodes); } + /** + * Sequence number of the root record. + * + * @return the current sequence number of the root record + */ + public long sequence() { + return seq; + } + /** * Reads a complete tree of record, starting with the top-level record. * @@ -127,9 +152,11 @@ private boolean internalVisit( final DNSEntry entry = optionalDNSEntry.get(); if (entry instanceof ENRNode node) { + // TODO: this always return true because the visitor is reference to list.add return visitor.visit(node.nodeRecord()); } else if (entry instanceof DNSEntry.ENRTree tree) { for (String e : tree.entries()) { + // TODO: When would this ever return false? boolean keepGoing = internalVisit(e, domainName, visitor); if (!keepGoing) { return false; @@ -163,7 +190,7 @@ Optional resolveRawRecord(final String domainName) { // vertx-dns is async, kotlin coroutines allows us to await, similarly Java 21 new thread // model would also allow us to await. For now, we will use CountDownLatch to block the // current thread until the DNS resolution is complete. - LOG.info("Resolving TXT records on domain: {}", domainName); + LOG.debug("Resolving TXT records on domain: {}", domainName); final CountDownLatch latch = new CountDownLatch(1); final AtomicReference> record = new AtomicReference<>(Optional.empty()); rawTxtRecordsExecutor.submit( @@ -173,11 +200,11 @@ Optional resolveRawRecord(final String domainName) { .onComplete( ar -> { if (ar.succeeded()) { - LOG.info( + LOG.trace( "TXT record resolved on domain {}. Result: {}", domainName, ar.result()); record.set(ar.result().stream().findFirst()); } else { - LOG.warn( + LOG.trace( "TXT record not resolved on domain {}, because: {}", domainName, ar.cause().getMessage()); 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 f6a583996d6..573effa173a 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 @@ -238,6 +238,7 @@ public void start() { disco, createDaemonListener(), 0L, + 1000L, // start after 1 second 600000L, config.getDnsDiscoveryServerOverride().orElse(null)); diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java index 3d4ed8f2bdb..d9b6820b2c2 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java @@ -16,18 +16,21 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.io.IOException; import java.security.Security; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import io.vertx.core.DeploymentOptions; import io.vertx.core.ThreadingModel; import io.vertx.core.Vertx; +import io.vertx.junit5.Checkpoint; import io.vertx.junit5.VertxExtension; import io.vertx.junit5.VertxTestContext; -import org.apache.tuweni.devp2p.EthereumNodeRecord; import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -35,39 +38,84 @@ class DNSDaemonTest { private static final String holeskyEnr = "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@all.holesky.ethdisco.net"; + private static MockDNSServer mockDNSServer; private DNSDaemon dnsDaemon; @BeforeAll - static void setup() { + static void setup() throws IOException { Security.addProvider(new BouncyCastleProvider()); + mockDNSServer = new MockDNSServer(); + mockDNSServer.start(); } - @AfterEach - void tearDown() {} + @AfterAll + static void tearDown() { + mockDNSServer.stop(); + } @Test + @DisplayName("Test DNS Daemon with a mock DNS server") void testDNSDaemon(final Vertx vertx, final VertxTestContext testContext) throws InterruptedException { + final Checkpoint checkpoint = testContext.checkpoint(); + dnsDaemon = + new DNSDaemon( + holeskyEnr, + (seq, records) -> checkpoint.flag(), + 0, + 0, + 0, + "localhost:" + mockDNSServer.port()); + + final DeploymentOptions options = + new DeploymentOptions().setThreadingModel(ThreadingModel.WORKER).setWorkerPoolSize(1); + vertx.deployVerticle(dnsDaemon, options); + } + + @Test + @DisplayName("Test DNS Daemon with periodic lookup to a mock DNS server") + void testDNSDaemonPeriodic(final Vertx vertx, final VertxTestContext testContext) + throws InterruptedException { + // checkpoint should be flagged twice + final Checkpoint checkpoint = testContext.checkpoint(2); + final AtomicInteger pass = new AtomicInteger(0); dnsDaemon = new DNSDaemon( holeskyEnr, (seq, records) -> { - System.out.println("seq: " + seq); - for (EthereumNodeRecord record : records) { - System.out.println(record); - } - if (!records.isEmpty()) { - testContext.completeNow(); + switch (pass.incrementAndGet()) { + case 1: + testContext.verify( + () -> { + assertThat(seq).isEqualTo(932); + assertThat(records).hasSize(115); + }); + break; + case 2: + testContext.verify( + () -> { + assertThat(seq).isEqualTo(932); + assertThat(records).isEmpty(); + }); + break; + default: + testContext.failNow("Third pass is not expected"); } + checkpoint.flag(); }, 0, - 0, - null); + 100, // starts the lookup after 100 ms + 5000, // 5 seconds delay on the second lookup + "localhost:" + mockDNSServer.port()); - DeploymentOptions options = + final DeploymentOptions options = new DeploymentOptions().setThreadingModel(ThreadingModel.WORKER).setWorkerPoolSize(1); vertx.deployVerticle(dnsDaemon, options); + } - assertThat(testContext.awaitCompletion(30, TimeUnit.SECONDS)).isTrue(); + @AfterEach + @DisplayName("Check that the vertx worker verticle is still there") + void lastChecks(final Vertx vertx) { + assertThat(vertx.deploymentIDs()).isNotEmpty().hasSize(1); } } diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DnsPortSplitTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DnsPortSplitTest.java new file mode 100644 index 00000000000..945b40c04e9 --- /dev/null +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DnsPortSplitTest.java @@ -0,0 +1,38 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.p2p.discovery.dns; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import com.google.common.base.Splitter; +import org.junit.jupiter.api.Test; + +public class DnsPortSplitTest { + @Test + void hostWithoutPortShouldBeParsed() { + final String host = "localhost"; + final List hostPort = Splitter.on(":").splitToList(host); + assertThat(hostPort).hasSize(1).containsExactly("localhost"); + } + + @Test + void hostWithPortShouldBeParsed() { + final String host = "localhost:52"; + final List hostPort = Splitter.on(":").splitToList(host); + assertThat(hostPort).hasSize(2).containsExactly("localhost", "52"); + } +} diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/MockDNSServer.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/MockDNSServer.java new file mode 100644 index 00000000000..6c039649619 --- /dev/null +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/MockDNSServer.java @@ -0,0 +1,632 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.p2p.discovery.dns; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.google.common.base.Splitter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** A Mock DNS Server that returns fixed TXT entries. */ +public class MockDNSServer { + private static final Logger LOG = LoggerFactory.getLogger(DNSResolver.class); + private static final int MAX_PACKET_SIZE = 512; + + private final Map txtRecords = new HashMap<>(); + private final AtomicBoolean started = new AtomicBoolean(false); + private ExecutorService executorService; + private int dnsPort; + + /** Create an instance of MockDNSServer. Add TXT records that can be served by this server. */ + public MockDNSServer() { + // snapshot on 2024/05/27 + txtRecords.put( + "all.holesky.ethdisco.net", + "enrtree-root:v1 e=QXOF2GWVHBKMKW57Y2KSKWYNFQ l=FDXN3SN67NA5DKA4J2GOK7BVQI seq=932 sig=DuA35BkYo9-FBwJ6MPxdNnYfcGMSGunAKUyfNN2gYQhYDBCPFZkr_cfe40Wspl2Vl76w6Ccs-B8ZrXpI_YymrAA"); + txtRecords.put( + "QXOF2GWVHBKMKW57Y2KSKWYNFQ.all.holesky.ethdisco.net", + "enrtree-branch:A63OP4WTCB3HGDZE4NGDEID6Z4,PL27G47LAASBPPAMXUDIJ3OCRQ"); + txtRecords.put( + "A63OP4WTCB3HGDZE4NGDEID6Z4.all.holesky.ethdisco.net", + "enrtree-branch:QXKEJG4XZEQSXNUY7JJYCATPEI,HIDVATDVB36L2MASAWA7SBJAII,CHFGCI2RQS3XFN2MKFP6G2ZM4U,GSONRYZILMGUJEN3PYQXYD6GYQ,OSHAABXYJSRWW35VOMYHZUKJXU,RUJF27NEYDBNQAFMI6K6SKDVRY,TXAFU343ACQLSVIMT5LYG3W2AE,R6EQA5KQEQM77JJXB4BHHTDF6Y,CF25LCQ452FBTR24CZU4C2"); + txtRecords.put( + "QXKEJG4XZEQSXNUY7JJYCATPEI.all.holesky.ethdisco.net", + "enrtree-branch:FEORTRPBZJSNRB3XCLK7KCMACQ,2M2GRZYAFKAVHHU5HTLUHPYHCM,KEC4722NVHE3KX3IYNZC34C7NY,NZCRGKUE7ZXSIS4MIQ4WUS76BA,3PYSNKXWWSRBWV377EFNYINHSA,C7SDD5OPASV7B2XUOXF5NLBBKU,QY2XP6FTX7QKFNP6TNZ2MOHZKQ,76OAILAEE52CRVMYFBIBMSEUXE,2QHFEUHLG6Q35B5LUS5BF3"); + txtRecords.put( + "FEORTRPBZJSNRB3XCLK7KCMACQ.all.holesky.ethdisco.net", + "enr:-KO4QDO4oH5cjhncisJSk1SyGmwJ5VFjNetXw4OSSqDKc0NMDGXYYl6wUjbhcvzADYDpE1Br3lRG-1xV6iA1OAyXSAqGAY1W3CQQg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEVfkOJc2VjcDI1NmsxoQKkjEwDL0wuG0AH3RWw1wRrYHdPa8OOL1Ko4DYcZeQjQoRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "2M2GRZYAFKAVHHU5HTLUHPYHCM.all.holesky.ethdisco.net", + "enr:-KO4QIPWnAyt-tyaS1cnMwEJaFdvDd-MpwEcVv26cfaZ3Ffkc4WCpHqzPmd_SifEsxQf7ontOz9a1EPjbxlsu_V1XZqGAY1UHtm_g2V0aMfGhJsZKtCAgmlkgnY0gmlwhCU85MWJc2VjcDI1NmsxoQNyzc5NiamRt8y5TQ9Qj-EZAYeCb7ZaclF5sOdPrErpbIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "KEC4722NVHE3KX3IYNZC34C7NY.all.holesky.ethdisco.net", + "enr:-KO4QMjtE87kVQpcuRPXV31w7fsvyit4Fw995wyZ_h6uK8atWGOhXpsi3xXsBQLgJFBWmNHgTw-V4JvZNFZkFHLU_8mGAY5hRoXMg2V0aMfGhJsZKtCAgmlkgnY0gmlwhC1Mp4CJc2VjcDI1NmsxoQPETNdXN5Cshfr8-rIIMev6E_MGqD-jzqcXW2yfzT3C54RzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "NZCRGKUE7ZXSIS4MIQ4WUS76BA.all.holesky.ethdisco.net", + "enr:-Je4QDqwqlm7UBywmVlR5UkkrQrg5B3UpkFexP7ucg4RAsdlMiJ4J1S5jT8jp6je6igMZ3OOnggdpd6l7QtQeNBICycDg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFs6LuJc2VjcDI1NmsxoQMgMreR2XspGJphg3fToxGKcMwWPE3e0gyxcqiQNLQXNIN0Y3CCdTuDdWRwgnU7"); + txtRecords.put( + "3PYSNKXWWSRBWV377EFNYINHSA.all.holesky.ethdisco.net", + "enr:-KO4QODvXk9lfhLNmdyttodPJQOIYQM36Rl9OZsFBZ4vMjaJcoJUXxTQCMqGLNTWmFh-1oEy0XoYKgPy4tywLZZcB8CGAYsolPILg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIe1OV6Jc2VjcDI1NmsxoQPj1f0OOW-g_vRGcihewV9-kcsQZdwXBHy4r8vwinurzoRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "C7SDD5OPASV7B2XUOXF5NLBBKU.all.holesky.ethdisco.net", + "enr:-KO4QLo95TIKo_axZA9xafYgl9jQ2ZTDFCQ29znjUwp-6zPUe6o8L9NO3X3n3uMZH8bFGnRmoIhyHLxNoOp2BU6O7xGGAYwoJEiug2V0aMfGhJsZKtCAgmlkgnY0gmlwhKEjElWJc2VjcDI1NmsxoQPIffRacW7k1QukSBfJ8leopO1M5wOv89G06m0SAQDB8oRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "QY2XP6FTX7QKFNP6TNZ2MOHZKQ.all.holesky.ethdisco.net", + "enr:-KO4QFsIg8OnqK5HJhhCaEuUTJ7RkLcTAXnUOOcaK63Ra2Kya2HsYSRgM1jRyr9utt3ib83q4fsU_IOg7kuR1jf2u2SGAYray1Tfg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKI2Jc2VjcDI1NmsxoQJiD81UDxWESi3paPaopKxnoF5vf0GAxlroaPr6bSIykIRzbmFwwIN0Y3CCdn6DdWRwgnZ-"); + txtRecords.put( + "76OAILAEE52CRVMYFBIBMSEUXE.all.holesky.ethdisco.net", + "enr:-KO4QDRyM88zIGJ2P0Ya1W8fucbp8kRTbVzV8k_2eawB0W3ZHMQ3o0HlbXVw3j1rMgN167LmsaMt1iO015HMVKGT2GqGAY0rjdqhg2V0aMfGhP1PAWuAgmlkgnY0gmlwhKFhcJuJc2VjcDI1NmsxoQJ2emahe6-2fq_hQqxm99rgYi4TSzQ1ky4utO3Tcpe-yYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "HIDVATDVB36L2MASAWA7SBJAII.all.holesky.ethdisco.net", + "enrtree-branch:N7HAL5M6HNZBGTWM3LWFDRX4WU,PX6K4ZETH5UX56IKHKL5TNOOMU,I2Q5U7BDYSM7O3O2IKIRL3KTZY,O4SSPZG3DFA7PHZ5L42DVOPWDM,Z5DOK3IB5X3IBOVVIK5MQCDYNA,ZJOONJUDYAQ53XOJU4KG5W4ATM,NLYAF5XVTTQUOMK4LYUF3FXNPE,XPX2T64BHDYLVGT5UC4GVIQIVY,AHB67R6KGAKWRSBL7CRAO3"); + txtRecords.put( + "N7HAL5M6HNZBGTWM3LWFDRX4WU.all.holesky.ethdisco.net", + "enr:-Je4QI2f2MAXFFi0Q3--GhXVZwNP-jI-G6XwmiDXxT-iobTTKOBJCyWUPk5WSoTtRS2ABqJmTAK8jflXBWLHuY7AueRFg2V0aMfGhP1PAWuAgmlkgnY0gmlwhDmAXJmJc2VjcDI1NmsxoQIc4YD6HsNIoj2HVJJDzr3cSfNtcEBRLtk5kgwXNH0q8YN0Y3CCXoeDdWRwgl6H"); + txtRecords.put( + "PX6K4ZETH5UX56IKHKL5TNOOMU.all.holesky.ethdisco.net", + "enr:-KO4QJqslvj0RYuX2CI61yCK7VuyY9Ik3c5EHtpkKehriXyqchhCDMx4sRuewcCAiWO_frsUevV9GXMPnsO2nzCrgFGGAY25poLHg2V0aMfGhJsZKtCAgmlkgnY0gmlwhKfrsZeJc2VjcDI1NmsxoQOe9l9K5UW0RAuQTH1q6CwG_UzbJyKLGSt4lUt5_tVmyIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "I2Q5U7BDYSM7O3O2IKIRL3KTZY.all.holesky.ethdisco.net", + "enr:-KO4QIvQh_txoF6gwXTRsMzzvHPdwQrkCHQYt4egwTWqUNQiMQh1yJ71U8UXs8b9pP9R1mUDTaiQ7-e0eB2Zbvouyb-GAYvSz8f0g2V0aMfGhP1PAWuAgmlkgnY0gmlwhJVmk6SJc2VjcDI1NmsxoQIuT88b-jLJBI40hV4VY3YeJnmYT65v2QPjyzuNG5dyF4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "O4SSPZG3DFA7PHZ5L42DVOPWDM.all.holesky.ethdisco.net", + "enr:-KO4QMrNy68H-dYJbPM7snJ1UrV-TPXA012sdna6WktkfksMED9fE3g-kzp7gBgOFEdFi1KIiEv5MXXAoOt1KOMVc1eGAYw66STEg2V0aMfGhJsZKtCAgmlkgnY0gmlwhF_ZxOCJc2VjcDI1NmsxoQJUhK-aKtl-Gc3ZnfF2k7oLFXmpGCHEi1_fBxAEfc1P54RzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "Z5DOK3IB5X3IBOVVIK5MQCDYNA.all.holesky.ethdisco.net", + "enr:-KO4QJjEv14HV0BgdPwIa0iO-xrrwoi9k9LJRw3utIUVib5dF6s_b3y8z0NP2VyUGsJDFOwuVQq6OC6hb5_hFUqnf1yGAY12fbSog2V0aMfGhJsZKtCAgmlkgnY0gmlwhCU8-mOJc2VjcDI1NmsxoQIT2maaVlQBw4oD4loM1Q7gygn42pTQn1Sydo74LtZAZYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "ZJOONJUDYAQ53XOJU4KG5W4ATM.all.holesky.ethdisco.net", + "enr:-KO4QBGQGqvIoSv3C7d-bCiMst5GGEvIQaQ5pAHs5On0MeNfcVjcZgd72l2UUoqf13HoKHUnVG37cmHw-11H4Y59F6iGAYrbEhVng2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFtO36Jc2VjcDI1NmsxoQJ3QRuoMLUn6djLfYCFTKSV8kUWRQTOethOOhLDW6ezdYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "NLYAF5XVTTQUOMK4LYUF3FXNPE.all.holesky.ethdisco.net", + "enr:-KO4QMiia-4vyLiHQQhxlbmCKdOTRqOiZin-BdAdi18LxNqqHEiuIQQ-F1dZu8sKi63vEk9zwr5gfMnxXQZCThEaiDSGAY2UmHG4g2V0aMfGhJsZKtCAgmlkgnY0gmlwhMb0_ESJc2VjcDI1NmsxoQLb1ZwjPQw7AjCvlHQpt9bmePVD85rbHbnFZ0naOB1RNYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "XPX2T64BHDYLVGT5UC4GVIQIVY.all.holesky.ethdisco.net", + "enr:-KO4QMlMC7rvmM9tvHXvwo2Vj5x5FePlyvH_6lBSCgZHN8HcWqtppk1peQlK9Ge79ma4j3gx_zX0hgJvprIy1ehjXXCGAY3Mer4jg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIe1FCCJc2VjcDI1NmsxoQL3IIF3eENfSsr5tih2ebcm3dWL-otLdZYqUvfJ-5_9U4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "CHFGCI2RQS3XFN2MKFP6G2ZM4U.all.holesky.ethdisco.net", + "enrtree-branch:XIOWDM2BTMANILG2M4LCMO7W3M,4LPHENNNENCUT7P3MWTB5IFRTY,5NQ4MKTYBPBMTGUT5IKUAXEGBE,CVK4DBIVPD2JPJOPQJQX77ZAWU,WLME2JINTIQACAMD5WZUQ4PRJI,DQYYUE7KVEXGEYBH4V2QHI6P7M,45V6KFI4NL43JFTJHL77KP6MQA,QDMF4HIR2UEXRJVOX4UJPJTPKA,BMVEIV6PNSN6RK5XEU4R4K"); + txtRecords.put( + "XIOWDM2BTMANILG2M4LCMO7W3M.all.holesky.ethdisco.net", + "enr:-Je4QCvStdCVA-jcYsFoXmDXjXlp-Cd_9sSMdi9Y1LM3tGH2C3k49yrWy-Y2Jg93ikASYljvD9qVUES0T9q5htcbL60ag2V0aMfGhJsZKtCAgmlkgnY0gmlwhLkIa-2Jc2VjcDI1NmsxoQKcjBIYl8vFVYVBs5trEl-Zn3IE7u1ZiiV986p0QCrAL4N0Y3CCdl2DdWRwgnZd"); + txtRecords.put( + "4LPHENNNENCUT7P3MWTB5IFRTY.all.holesky.ethdisco.net", + "enr:-KO4QF8nbKqJkOeYlF-akOVBYzusVwSjI_ra_t7TmM52mEisQLCwNAjujSSc9-6ECErhss-gVTgBDzhY5QmVcF0PuZOGAY3Li2SUg2V0aMfGhJsZKtCAgmlkgnY0gmlwhCL__BOJc2VjcDI1NmsxoQLHimHBtxJxch5bc20e_O8OdeN3UQx6TQQWqaYfyi66OYRzbmFwwIN0Y3CCdW-DdWRwgnVv"); + txtRecords.put( + "5NQ4MKTYBPBMTGUT5IKUAXEGBE.all.holesky.ethdisco.net", + "enr:-Ke4QLrsveUYt2tacm5EZETFc1F3EyvNYfRRkRhljyeLMIzccRlPI1kKmBWuELQs5iAIRZgv92P3Fxx_zJ3xyUbN3muGAY0x7p4Ng2V0aMvKhMYaYJiEZRbqwIJpZIJ2NIJpcIRU94PXiXNlY3AyNTZrMaEDBxF5W6guB9qZoR-c_zuDUE1UHyaH0FMjJKHC9Jq4ji6Ec25hcMCDdGNwgnZfg3VkcIJ2Xw"); + txtRecords.put( + "CVK4DBIVPD2JPJOPQJQX77ZAWU.all.holesky.ethdisco.net", + "enr:-KO4QH3RGFoO4KTuq8gxJC_mjnPBqNaDvoX_xsuYbDOqvJA4CPJs6nifZuqE2sTB3O-kHvEI0dbFJ87FoWkKhgmXYuOGAYrbEhVBg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEV7HyJc2VjcDI1NmsxoQM1AMXary_Hiw-yesOw24q7GwMH_DmNHHitFPU46foJ24RzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "WLME2JINTIQACAMD5WZUQ4PRJI.all.holesky.ethdisco.net", + "enr:-J24QIMYTLuDOO1F5w8AL_WGXU6CZmzi05W35Rz8PuVT8b6vUqmUatifQtzuL2Q7REuRY5kYNurUbLHscGx3j1zjTmmGAYrbbtBig2V0aMfGhJsZKtCAgmlkgnY0gmlwhETp6VKJc2VjcDI1NmsxoQKrwy6w-9JGp3dagVeXMZEd3iZGff-rEVTqd4gmkhBRBIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "DQYYUE7KVEXGEYBH4V2QHI6P7M.all.holesky.ethdisco.net", + "enr:-KO4QBhk9j1k2tfVrwV4yVRS9jn2zwS9KKADlhqvMsZsqdXFZiz0s-vCAnqtkDfszY9peGDBiWulLdNe6KFuvqgxgy6GAYtHL5rAg2V0aMfGhJsZKtCAgmlkgnY0gmlwhDmBAQqJc2VjcDI1NmsxoQLXrL6FtBz248tpC2_DxJs2HsvtHdSoNsZIBwth_MMu94RzbmFwwIN0Y3CCdmqDdWRwgnZq"); + txtRecords.put( + "45V6KFI4NL43JFTJHL77KP6MQA.all.holesky.ethdisco.net", + "enr:-Je4QEedukMKHefwNNtNam8wvx8_0GMFHLC9nA-TWWPMTZ9FD4C1CrjqFxLNruZPGL4T1ApKPtes_ApKbwwOEc4dNxIDg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIrJ_vOJc2VjcDI1NmsxoQIKnge3StO67CequDTr_QwD_V_qhS7TnGO-tgE472AFroN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "QDMF4HIR2UEXRJVOX4UJPJTPKA.all.holesky.ethdisco.net", + "enr:-KO4QCeMBK9Ur5AHbkdov7TdALRsHzc48RXNtvR861fcmcxPYH59PthmWiT_pQHfVOH3x9-HK2bG8_h2jm46sZyJ6UeGAY2ZCasag2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFs7nOJc2VjcDI1NmsxoQLT22lM9abRweZb93jeMd4jvuRsXnjQFT2oY5a0rb0cvoRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "GSONRYZILMGUJEN3PYQXYD6GYQ.all.holesky.ethdisco.net", + "enrtree-branch:V4A7I7I2SFVUDFFH7AGPFXACBA,67FFSS5IYEYRGUYFEWGIJDIMII,NXU6DU7HEM2L4DN6G2VHHFT72I,3T4CNEKYCJP2PJ3EQVJSFKHH44,KYTZHQX2PSOGY6RQTCVTUKTS7E,GUBLA7OPRPBMYMQ76EXDMB7BHY,HGROZTZI7YPDW5F37QVUPQEBG4,AB27AIPX5Q5J6UAXQ6C2QFTYZA,7UIVPNORDS3RPPTLSNGYAN"); + txtRecords.put( + "V4A7I7I2SFVUDFFH7AGPFXACBA.all.holesky.ethdisco.net", + "enr:-KO4QMIHR0XgkMyX4E9Xh8Fw3rFr-QWyH49lpFmrEhx1Cc_WMxm6atHPm0g-bXmzradBORfb0S9_6dyi-GnQbtlm7GOGAYr8tSZLg2V0aMfGhJsZKtCAgmlkgnY0gmlwhA_MQUKJc2VjcDI1NmsxoQMrgP3au96duBa9PQzAFPhlQ7ikkA96YWAkrItcm5zCxIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "67FFSS5IYEYRGUYFEWGIJDIMII.all.holesky.ethdisco.net", + "enr:-KO4QHY48cNs9Lauv6W3XCDcuBvhJbmL-0MAASBah_V-jG6iODMGVIZ9xv4K3MHSqQQht3Fc39CjU5zoYROe6D8uOq2GAY2UXNmdg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMb0yKiJc2VjcDI1NmsxoQNjc7k9S4kjTIwltOf7EH2ZmK3vZiEBpxxNqondpU3qV4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "NXU6DU7HEM2L4DN6G2VHHFT72I.all.holesky.ethdisco.net", + "enr:-KO4QLbrxgLUdY0NhpBPVfilpwn4d3gPFIEqoq62P1piOkVmGwh5qpbKtor5FfE4FM_eezlzMdeCM7bzau8ciKZU1GiGAY0-AhsZg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEp2iPuJc2VjcDI1NmsxoQOGZRfYj6RgBYWulaw90MVPDt_9F-oM3PzevNe-RS6KyoRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "3T4CNEKYCJP2PJ3EQVJSFKHH44.all.holesky.ethdisco.net", + "enr:-KO4QKNzTVC-wa7whLxFFss7NzEFDdVuAjgEebNGGK9xTWKjHTjqmMNfY4_h90iqlSYM_LoGyH2FFXhbHT05zKyALMSGAYuqLQ8Sg2V0aMfGhJsZKtCAgmlkgnY0gmlwhA360gaJc2VjcDI1NmsxoQPSUi99W8lkrquAhLsmslwXU4-dxxVfied-w_yqYEXpU4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "KYTZHQX2PSOGY6RQTCVTUKTS7E.all.holesky.ethdisco.net", + "enr:-KO4QJn2ahKcTeE2karHSHOiJgE9b2BJWo54NS3nzjjCDnsvZ1JjDl8yulvzn8qQA48zCtHkiVMEVNHtdf_LM_N0Cs6GAYxElMbzg2V0aMfGhJsZKtCAgmlkgnY0gmlwhJ3m3GOJc2VjcDI1NmsxoQJzs6KencrR0_ylBVNGuZaknO_zQUYOrq4rjj_FmTlHN4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "GUBLA7OPRPBMYMQ76EXDMB7BHY.all.holesky.ethdisco.net", + "enr:-KO4QIFA0MiYPPyzvlUqi5j1dL1RGz6MdFhhLN30iXeNc1JfCS1QqTJciby7ZhlQYwFdVMuk_ptgu530WxiQR-UmZpWGAY2ia8cWg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEt3l4OJc2VjcDI1NmsxoQJRcQCiMhxUex-gtsxf2IWJ6nGita_F8BaPZxTwE310WIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "HGROZTZI7YPDW5F37QVUPQEBG4.all.holesky.ethdisco.net", + "enr:-KO4QI3-xQxY2u-misXaxFtpfezZr0Fk0jiL6U7ZAbwV4yCRcWxgNVOxLifoUOm9uMUqH4wlGxIYL7onmzlavqRIjDGGAYs5av4dg2V0aMfGhJsZKtCAgmlkgnY0gmlwhJRxqGeJc2VjcDI1NmsxoQPc4dhahamqQVxv9D89fNMO9DOP_YaPJRQ3EmT2XantU4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "AB27AIPX5Q5J6UAXQ6C2QFTYZA.all.holesky.ethdisco.net", + "enr:-KO4QNZrQF9iyry46odExoUh3kkHjWWHS0iEEjc8n2R4Orq8Rov4Ar3ozsyWt9Z-dmBtpTfmAo3UYspLFcvtHNzwjPmGAYwWbywRg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFtbbyJc2VjcDI1NmsxoQLD-zctKWrecek1fu3o2IKHUY9rkd0BYeQHrzpqElk_1oRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "OSHAABXYJSRWW35VOMYHZUKJXU.all.holesky.ethdisco.net", + "enrtree-branch:LHFO3FXNRHB5B3YSTKUALGMEAI,5FNWKKKVUPIHRKWZY5P33A7E74,NHLNHXPRKOALUKH4MZ7CIVDONU,BAY6SKB2RTQCM7RTSHAFQ6TTBQ,ERETBCE2BAPJRSWIEUQV3QPP7M,MFPFKTJREYMESD7STJJOFYDVV4,MOBGCDYGQA4CNBQZDPN52JC5E4,RUQLNXP5XBWPAOEKRUEBAQXXXU,NJCWGLUAGBNZQYPU752O5L"); + txtRecords.put( + "LHFO3FXNRHB5B3YSTKUALGMEAI.all.holesky.ethdisco.net", + "enr:-KO4QEuoZibAJnKlhcAENpJiju6nVH5J2or_xnVnX_eputCuTHBVtNJmoRKgDBU0idG4kbxFPPKaWRQ3TdYeIbsAAV-GAYrax-PMg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKIOJc2VjcDI1NmsxoQNCYX5kXJqI2deaVOtsTmUQka7dIocapHSAfnNn_CTL2YRzbmFwwIN0Y3CCdmCDdWRwgnZg"); + txtRecords.put( + "5FNWKKKVUPIHRKWZY5P33A7E74.all.holesky.ethdisco.net", + "enr:-KO4QMD5jLnGVTSO75tQPB1JOCFKmtSPRi5pmE7wMwjDRfVKdwpZJvr8qfv1WmfRnJFzH5qV2sfC187nT9mp-xE_-gGGAYupHwgxg2V0aMfGhJsZKtCAgmlkgnY0gmlwhE6KPgeJc2VjcDI1NmsxoQP9YVLocwkml9Z9YNDRpmzVceRY-Ts45qiPOBC1OsxuUIRzbmFwwIN0Y3CCdmmDdWRwgnZp"); + txtRecords.put( + "NHLNHXPRKOALUKH4MZ7CIVDONU.all.holesky.ethdisco.net", + "enr:-Je4QBgb-GKZRRjU2TAE_9KyBHC8ImLYDPG8KShRKx8gbKF3F-gpoHiyeprH42weMNSg6i8GeG-n0SxwWJRe6zyXj88og2V0aMfGhJsZKtCAgmlkgnY0gmlwhCV4sKqJc2VjcDI1NmsxoQNuVgNhx7pgtJotEBq56F9YFom-6szZ97gCkea35_lDkoN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "BAY6SKB2RTQCM7RTSHAFQ6TTBQ.all.holesky.ethdisco.net", + "enr:-KO4QIo--Wic2Dyk2II8x2uTPNOFY3CRZ8wmABP5qOhmZv4LXvAfbdISPp7HDrJwrejs032LHH5rhUY5l8bj9-M-_u2GAY3GDGQWg2V0aMfGhJsZKtCAgmlkgnY0gmlwhI6E2M-Jc2VjcDI1NmsxoQM46NJn0fjSRwgGkB0G1n7QQ9VyBDwiPXPX_xgM25tDOIRzbmFwwIN0Y3CCfEyDdWRwgnxM"); + txtRecords.put( + "ERETBCE2BAPJRSWIEUQV3QPP7M.all.holesky.ethdisco.net", + "enr:-KO4QJY9GUtSCCXanmC9p7LYAfqk7jOUF-zxl7zd2ce5lD_aSNk9KfzBbh-Hii3qMzv-2x4a0Xal7RQMzQv9BqCkUMeGAYw1cGzgg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEVR7SJc2VjcDI1NmsxoQPQjk4qnGis3WKuSQDD20H-9n9xKFTbnLsB1DhAteyYk4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "MFPFKTJREYMESD7STJJOFYDVV4.all.holesky.ethdisco.net", + "enr:-KO4QJfXFSQOpwMtEgz0BSB0gJ0zXQZ6yMzoBfXYCK3ATxulM_mkkQhMWUHsrNyNH9cQYfxRbN_hEGfP3id3IfLiv7KGAY5iEk-hg2V0aMfGhJsZKtCAgmlkgnY0gmlwhGQmekqJc2VjcDI1NmsxoQOoFPqpQmRhcIfVINZ0knq8hxe2tUYnTSLEC-6Xoly7jIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "MOBGCDYGQA4CNBQZDPN52JC5E4.all.holesky.ethdisco.net", + "enr:-KO4QDuar8SHEX1o_kUIKB0IajdUqcRb3ZkZdr5_MIxT1BUkLVk8AelLTv-_ioVGRAwNIJTzT4m8nyzV1VbsjjpeZ0yGAYrXy6Pag2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKEeJc2VjcDI1NmsxoQLy0OM9Ze8JjVHZhLQg7XejbI5iedlSDZNRmtQdTy_X8oRzbmFwwIN0Y3CCdn6DdWRwgnZ-"); + txtRecords.put( + "RUQLNXP5XBWPAOEKRUEBAQXXXU.all.holesky.ethdisco.net", + "enr:-KO4QPpoyIf1DfiiOW8Tt34tBuA68Qd55cyWcqGOCFJtcHKtcqYr373NJPyQVd8ktsuqZU8L_ERSRAfKesEwdBARg3OGAY45u3jEg2V0aMfGhJsZKtCAgmlkgnY0gmlwhJRxo0WJc2VjcDI1NmsxoQKhMTQ31q7EAIHkisiA02KVyFZZLq2Q9w4_jgmiYCsF_oRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "RUJF27NEYDBNQAFMI6K6SKDVRY.all.holesky.ethdisco.net", + "enrtree-branch:OOQAXEUVKR5EJ722HM2UANT3R4,HOXANHAMPVCFA2QR54VEMWCWPE,INA5QYTPU6WIOBT7IC7U3WGDIY,3PMAED3BC2HUS7AMEPGJDKENQY,VJJPHI6PWJ7NFBTW2XJH3JMMNY,5ZWIGTGKMFAFF4LAODABANSBOQ,UYESJQUB2BHP2MSJNUSDRGXQ34,CK3HQLGRQATDVF5CEGHX4DVQRE,AYB76YY6WPJVIBLRB5U7L5"); + txtRecords.put( + "OOQAXEUVKR5EJ722HM2UANT3R4.all.holesky.ethdisco.net", + "enr:-KO4QBU5Jlmco2sEPOXNEctSfBJfxdgANrWXVLS-BVDQYrU3N_oaeceDQIH82zZ6BSi_F-FQpSKhEVYtPDxI1BA4OW-GAY5bg91Qg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIrJH32Jc2VjcDI1NmsxoQMXPocPKx0wRocEEzASwUfzDLKd1f-Jifv2pyIu2M8vaIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "HOXANHAMPVCFA2QR54VEMWCWPE.all.holesky.ethdisco.net", + "enr:-KO4QELWztCXUz9MCxD8zbh65aa6LwP3wubhdXJVdlyrl7QzFJkUXosDXndbQqtAT2qD5nXAFpBnz5MeUSZf_GfjkI-GAY16vTc2g2V0aMfGhJsZKtCAgmlkgnY0gmlwhCZhyOSJc2VjcDI1NmsxoQMGwfOevbfZOGp3bL_2AH1SBdx3zn2_l56uG_4CWDf66YRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "INA5QYTPU6WIOBT7IC7U3WGDIY.all.holesky.ethdisco.net", + "enr:-KO4QDpTzc6voIJiaQO1T8cbJvPej2OvifSuuDkQVrAcC3imJXT9J9zt5DelQaMAeu_uk11kFFgYJHBAE88J2cvxYGqGAY1olkLcg2V0aMfGhJsZKtCAgmlkgnY0gmlwhLnRsD6Jc2VjcDI1NmsxoQKfdroTKrg5QqtuIyfF7LivDR4GjeBnA8xQrAg5ma4pzYRzbmFwwIN0Y3CCy_KDdWRwgsvy"); + txtRecords.put( + "3PMAED3BC2HUS7AMEPGJDKENQY.all.holesky.ethdisco.net", + "enr:-KO4QFMsKiZk64UQsyLVHrxTpTpmdZYzuI-Xgy0gut5NgLulRvbrgQDmdrAT2PQuxK8K9AiplRzJ_i8CtseBD_d2bwGGAYx9HQirg2V0aMfGhJsZKtCAgmlkgnY0gmlwhLBn3miJc2VjcDI1NmsxoQJSdj6d-7J6YsK6vZwRl_zzMSCKauLadYxL8zfjjzOQZYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "VJJPHI6PWJ7NFBTW2XJH3JMMNY.all.holesky.ethdisco.net", + "enr:-KO4QHCu9ZPe68fKUXeUzje0Jwv0D8UCrAQ_K5W9KHrIpTdUYWIGwbzyl8heZ1cxijlyUhN1WB0HZpEBThUmrdm-uLiGAYra3Fk3g2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKJOJc2VjcDI1NmsxoQP0AAgBs1kDG4XBe55HY0J4F7GjHiwQVUY-xFKYXOB594RzbmFwwIN0Y3CCdnSDdWRwgnZ0"); + txtRecords.put( + "5ZWIGTGKMFAFF4LAODABANSBOQ.all.holesky.ethdisco.net", + "enr:-KO4QFP5g9U1YtBLeJi56xoBll8E4eUJeAAYMoEg1l0xPXRybh0OYNZfteBjkHMwbucccNdGB0amzNR4XtxmAfjoFWKGAYrbEjd8g2V0aMfGhJsZKtCAgmlkgnY0gmlwhF_YY_eJc2VjcDI1NmsxoQJb-JLk8MFQQPMz3QI2ya4FtZdHqZ1Nm0xD4MtOYc9ifYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "UYESJQUB2BHP2MSJNUSDRGXQ34.all.holesky.ethdisco.net", + "enr:-KO4QB9gKzGbKvqoPJZymq3AiwZvWSHOup-36D8ec3CwNZDFRoEmp64UmWaP0St3OXDIV4Q2Rm0MnelRnAjvn3HRfyyGAY5EPB9Jg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIrJ3_qJc2VjcDI1NmsxoQKHt6N7ehDnwT8_lFh-C4KgqUplYwDvYy6rOP6DFRO8lYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "CK3HQLGRQATDVF5CEGHX4DVQRE.all.holesky.ethdisco.net", + "enr:-KO4QJy9FHDJZfMqgGGIVGn-j0rHI6JDjTWEo7sN1mBkzO68ZId8jkRQfgZ5dJkv4GGm2rsvrWk6OJYF0td7n0Ve9GqGAYu0QPj0g2V0aMfGhJsZKtCAgmlkgnY0gmlwhHTK4wuJc2VjcDI1NmsxoQIYyd-9-wdwfPPu6uRcnTKkQaCekdhR7488-2pxl36AZ4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "TXAFU343ACQLSVIMT5LYG3W2AE.all.holesky.ethdisco.net", + "enrtree-branch:3EMD3PTQYRFVNFBQ73BYFL4AEQ,PMLO73ISEK4Z6IRPOSWIUUKF64,5NHWEAJHKSAO5DIIYAZ7MQJVSE,LNO6EN3Y2LJA5YOJMEMODAJFWM,23B5UFB3JWETLIKE5Q6B2B5MWA,HQ5S2EHH762PSKVTHWNQ6346NI,O25DXMPE4UEZ6SFMQRBGL2E2I4,2WHIXLN5QX3JEK3SSH7MXKTJMU,WQDLDQ7U74Y2HM5LHVCOCY"); + txtRecords.put( + "3EMD3PTQYRFVNFBQ73BYFL4AEQ.all.holesky.ethdisco.net", + "enr:-Je4QFiHIu4B3YbbG75mlr58JRaqpGUQij1n1pN3zaHoWbGrShq8aYqipbkPLaINPTX02YBfchyACgLHgH9cMarIUapQg2V0aMfGhJsZKtCAgmlkgnY0gmlwhDNRao6Jc2VjcDI1NmsxoQJHJlekJODLxBDPwpVLBtTMizLq5o9JtXkD8MQpqAIUYoN0Y3CC2fmDdWRwgtn5"); + txtRecords.put( + "PMLO73ISEK4Z6IRPOSWIUUKF64.all.holesky.ethdisco.net", + "enr:-KO4QBxZ1JFNQEopzO-wMumFIw4fGHkuPZkuCLvgcz0X8L8yKWUkQ-UiS7-KHBCjGOby7yjR6m7m9dqoQvlHejBncSWGAYv2B0KZg2V0aMfGhJsZKtCAgmlkgnY0gmlwhA_ML-SJc2VjcDI1NmsxoQMEi6SKsKUVTnxDWKc1Go6ZG8o5nEapNJBaWAd_YC26R4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "5NHWEAJHKSAO5DIIYAZ7MQJVSE.all.holesky.ethdisco.net", + "enr:-KO4QMiOfa0JolrO470F-mRKslpq85BBGRz_JjoAOOnLuUdxBObP7579igrB-YGSdaUtb3Ih9QXBHRAl0PWYmE5NW32GAYtIvzSsg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMF6nk2Jc2VjcDI1NmsxoQI57ZbbGPYfl0DtrsmjeKDosRG14eV2qLI8AE6FsP2oy4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "LNO6EN3Y2LJA5YOJMEMODAJFWM.all.holesky.ethdisco.net", + "enr:-KO4QMIX_--Ar7-XnFcUVlJpBBUfZdfNOftELgvHhCGvbLGTMT_Yqs2M15BCG0qoHsYkVBP5b4wd2wqNyNIfFaTtQ7qGAYrXxOTEg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKEeJc2VjcDI1NmsxoQKG05NXftvaats582r82_zpOi2C54WjSeYwxKfSNeSJaoRzbmFwwIN0Y3CCdmqDdWRwgnZq"); + txtRecords.put( + "23B5UFB3JWETLIKE5Q6B2B5MWA.all.holesky.ethdisco.net", + "enr:-KO4QGjsmd6RDlGYHJu2JnI-Lf4TZ8s1yJtLDvvtp_Kw6xIwNn7_Ti_FmEHfWbaSyy0Icl4jlErvH-LrEmi9PCzB27uGAY0MEI03g2V0aMfGhJsZKtCAgmlkgnY0gmlwhNXvxn6Jc2VjcDI1NmsxoQOzxdn7S7O58IiaWGk4n3MsiB816GGv_bAd_PM9FkWeZIRzbmFwwIN0Y3CCVTyDdWRwglU8"); + txtRecords.put( + "HQ5S2EHH762PSKVTHWNQ6346NI.all.holesky.ethdisco.net", + "enr:-KO4QK883RN_CIppuOvhzi0O3Qg-XzHUZsdKIEqLdvZMLN5BCV5QTqqxRzJjtXa7NZjBi22UQJUUlsrQ_3g3Uavv92aGAY1BRdWag2V0aMfGhJsZKtCAgmlkgnY0gmlwhFJkOnSJc2VjcDI1NmsxoQNAyMtRruAQcNn10ZReZiiZtPnk35o_oJEhabuBKi1gLoRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "O25DXMPE4UEZ6SFMQRBGL2E2I4.all.holesky.ethdisco.net", + "enr:-KO4QLC4qJt9OIKLkxROeQMNcj4ZuH_QZsVcS1v-NhzBGAf8B-5xF3GYqtMp40n6nwDg8o1yUs7xEx0kgf6wBJF-AuqGAYrbCvwJg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKGuJc2VjcDI1NmsxoQPogWsyNFpt9VYFYrsdUS4dxK2BMeL4FY41ZzgBjeEGRoRzbmFwwIN0Y3CCdmCDdWRwgnZg"); + txtRecords.put( + "2WHIXLN5QX3JEK3SSH7MXKTJMU.all.holesky.ethdisco.net", + "enr:-KO4QF2wWLadNPZ_qwOIZkLxPEJjj3N23892JKdtSOeeC-X-V0bZsjQ3QqpidJviGT0WrBakFANcbSa9IqFSOslJLV6GAYrayMEMg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKIGJc2VjcDI1NmsxoQKQ_STV3aCaM5we4KxMPMpdG63xPZieKw8A36OR2tuouIRzbmFwwIN0Y3CCdmqDdWRwgnZq"); + txtRecords.put( + "R6EQA5KQEQM77JJXB4BHHTDF6Y.all.holesky.ethdisco.net", + "enrtree-branch:5AOSND63QKPVT6EWNMALKAFC4I,WAU46WR54TX2VBK4QM7NFTA73Y,BBNHIHYBPXVQTDZQ4JXK4QYDMM,OA4ZFFGZNFUC3LIYCUDJOPVR6U,KMY5HFBGCUDWN2ZXE6QNUQHGYY,OGKXEQEVGNJ574WCU7KFNTXNQI,CDXRYZFKH6OUWTA2IJ2MYIMYPM,IPU725BUGL4HIOTSOIH3KZG5ZA,G4MENE2MCGXFPYEBF7SQEU"); + txtRecords.put( + "5AOSND63QKPVT6EWNMALKAFC4I.all.holesky.ethdisco.net", + "enr:-KO4QKn6DlOQ0ybfLAfyPlyPssuWtP0Zu5FEUEgr3015XppiCP4yr9SeMgnpN90AVHJA5C61F3677GiO0N-JIXGfN2uGAYra2k7ag2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKJOJc2VjcDI1NmsxoQKgKJovbRS4hL3ugMVrevOCUGDS-ixgByq_tbh4T9oihIRzbmFwwIN0Y3CCdmCDdWRwgnZg"); + txtRecords.put( + "WAU46WR54TX2VBK4QM7NFTA73Y.all.holesky.ethdisco.net", + "enr:-KO4QE70KmVYr_jd9JOMBFTIJomf3oliyhugCm4kFnzYD7dcJk3y7dVYyakJdlWHBuk1t4hDjW05BzeVf2f-V0LQ-r6GAY0-Aab1g2V0aMfGhJsZKtCAgmlkgnY0gmlwhCUbP0KJc2VjcDI1NmsxoQMQLfyyzC-YY6A53fEu5a8tP-JWCUM3vkX-c7nRyu28d4RzbmFwwIN0Y3CCequDdWRwgnqr"); + txtRecords.put( + "BBNHIHYBPXVQTDZQ4JXK4QYDMM.all.holesky.ethdisco.net", + "enr:-KO4QCMGWCueZIoaBJyNAqOo_1wJ-QmbQ-qlTQx_rfz0gzKjF8VqFc99Q3ZMcfpK2qdLtWZHoRoiCkstH9_rWAU49DaGAYrbEkgdg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEVzHeJc2VjcDI1NmsxoQIy6rguhP2KemBUH2HJyW-GPo8prt6Ay9gjX3GugxLuGIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "OA4ZFFGZNFUC3LIYCUDJOPVR6U.all.holesky.ethdisco.net", + "enr:-KO4QFuTcCcZkhGlEZ9YjHVURcwzRDKDu_o8RBID9eF5lmG9fb9ZHBHnz0E-tXO4TcPfwxEtefRAaAVPj9Ycdi3OeXuGAYraylBgg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKFKJc2VjcDI1NmsxoQJRTkcLLW6c3J6kd1b1siWyumgXACvQK7ViQjsgMFx0c4RzbmFwwIN0Y3CCdn6DdWRwgnZ-"); + txtRecords.put( + "KMY5HFBGCUDWN2ZXE6QNUQHGYY.all.holesky.ethdisco.net", + "enr:-KO4QLBXgetH_VHZgzalnkfF-iibF4km3OCLzY1TK27U-yG0ZA7Z4C8DL-da5x65khpgvOBa8c2CXffZBdTtD2BZQCqGAYrg47Ahg2V0aMfGhJsZKtCAgmlkgnY0gmlwhJT7RHyJc2VjcDI1NmsxoQKeRdcysv01DZ_Mzk9t1jcWW-YTjP1fPIrHVpw9hc_Q_oRzbmFwwIN0Y3CCdn-DdWRwgnZ_"); + txtRecords.put( + "OGKXEQEVGNJ574WCU7KFNTXNQI.all.holesky.ethdisco.net", + "enr:-KO4QH_WNLhz4qmL-EOMCQFfhE3QOOvyG5uyX0RR77gYrsdcJnUxS5OBQWyMqhRQLO2P9htXeBxJ4kR9ez13qQRiBp6GAY3m5RB8g2V0aMfGhJsZKtCAgmlkgnY0gmlwhIj0RveJc2VjcDI1NmsxoQJFRRKo2aP4qXB8sZnA6_rJqKgo8FZGT2i3ipEzwXCayYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "CDXRYZFKH6OUWTA2IJ2MYIMYPM.all.holesky.ethdisco.net", + "enr:-KO4QExy2s5Uh2eTEJtsODBEOcPZZN-CYY0WEr_8nA_uK43QV3iqmFzfXJ-29zootm5F-E-DrjzTObp2tw_klTXPRu-GAY2ZtbV8g2V0aMfGhJsZKtCAgmlkgnY0gmlwhBJ2VQmJc2VjcDI1NmsxoQJuk1X-4JUVQT-mguV7dSGSUC-RoxdnTyOmD-VrfRFQyYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "IPU725BUGL4HIOTSOIH3KZG5ZA.all.holesky.ethdisco.net", + "enr:-KO4QLf3R97a5p3pRgY1AeN1DisWZUTnHbIMt2aPh0zvq8QIZguS9EiujNa71YXqJgxw7ztz3YmwJMWuPuhfWqSkxqWGAYrayxVYg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKIOJc2VjcDI1NmsxoQIHu5vOuFNLO8814yybwdh03yUQzfPTTyeK_X44Gz5jx4RzbmFwwIN0Y3CCdn6DdWRwgnZ-"); + txtRecords.put( + "PL27G47LAASBPPAMXUDIJ3OCRQ.all.holesky.ethdisco.net", + "enrtree-branch:3JSDZJCCWKIEBGPH5EI5VZF4RQ,XTJ3PFTPBB3ATDDWAA6W7RRKMY,FTTHOFPRPIHMJNZA3VZUAW5TIM,DAVKEW6RQ5YTYHKCJ2A2J5GVCI,TGKURK5IPVCPXS4QPS2KZJCP3Q,RWRV55FT3DKDVGZK7AEU2DR77Y,CTDMY3ALJA7FRKVS4MCTAJRRH4"); + txtRecords.put( + "3JSDZJCCWKIEBGPH5EI5VZF4RQ.all.holesky.ethdisco.net", + "enrtree-branch:4PFOQY7RKTNLRPXQXKKXD3K6LY,3AVV65MYCJ7AGFUCKXT2UX2DSQ,5JAERI2BPJN45X7IK6SWZOZ2FI,YUZZDZD57PTNEIB5QL3FEXK2ZA,TMY2W2YBNCXUUNA3Y7QXWVQLRE,MJB42632KKOPGJ2KNY3GPKP66I,QXRV67JYJNOYJDPC5CTKUVG76M,QJM2RVJT7USPDSI5VVFGGDW6JM,I33RZWBACJHNDEXG2RRXMX"); + txtRecords.put( + "4PFOQY7RKTNLRPXQXKKXD3K6LY.all.holesky.ethdisco.net", + "enr:-KO4QJ8BuQCt7gBXU_26FNx8waHuCzCkG54mErQoBAJrYaw_bdUJT0yQA0vG8gr-dUoS77OX0D4tD-R6e30832ncLMaGAYrbEjQ3g2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEV4tiJc2VjcDI1NmsxoQJHEsWJ3CoSi-1JgkeSogpyRqYvBaJI77-soOcbmB_nyYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "3AVV65MYCJ7AGFUCKXT2UX2DSQ.all.holesky.ethdisco.net", + "enr:-KO4QEaesvQ1kySNS_mnQfiBr4DZQmkhHmqKEGRQvgyd5K3lNDm3vjs9cMMVTY9FgENpP1o0UGBsapNNfvXIN2gaudGGAY3ulUKIg2V0aMfGhJsZKtCAgmlkgnY0gmlwhDIjTkmJc2VjcDI1NmsxoQNVbUX0SvjLX7d7psFtTTNkKT7x_zHl3f4bSVQrkbK1YoRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "5JAERI2BPJN45X7IK6SWZOZ2FI.all.holesky.ethdisco.net", + "enr:-KO4QEsCIXXCDLUsTbUOT3ILg3rpga0gZXaG9_YT9_kFk97iRwu_Pr4RlhDvdkg4Y4bl9SBVzP9bFtL7H9AU7QxE7OeGAY1lbo59g2V0aMfGhJsZKtCAgmlkgnY0gmlwhA3Xl1-Jc2VjcDI1NmsxoQPsZi79eOcjTI3Xa2GCBiXRCgtm7wFo19cVlmhQSZ-i64RzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "YUZZDZD57PTNEIB5QL3FEXK2ZA.all.holesky.ethdisco.net", + "enr:-KO4QKuFVKbKbtK1djMy6q3TLuAWYDAjoC20cYPdhcOk7PhUdA67TXr9vTGfZZ9AO7ivmQZyRW4w9TFk-29_xP72bkKGAY2EAMvIg2V0aMfGhJsZKtCAgmlkgnY0gmlwhF_ZKGKJc2VjcDI1NmsxoQN4FBojS_W4gviNwaTDsIXiBEGXaQMtUzxQHwKWDmMR74RzbmFwwIN0Y3CCequDdWRwgnqr"); + txtRecords.put( + "TMY2W2YBNCXUUNA3Y7QXWVQLRE.all.holesky.ethdisco.net", + "enr:-Ke4QEsOrFCJqnbVXiuSh3DKjvS3RlUoOrrLRlKp2mAqmCuJQBVLFREqABNWRvZfLOxDPEtvcRzDtDW2juSa1vd2xj6GAY025-JNg2V0aMvKhP1PAWuEZcNqwIJpZIJ2NIJpcIRU961viXNlY3AyNTZrMaECoqR94LV1BQMpEDbmfJdd8adEPWHV7qP09nePPVm9nLqEc25hcMCDdGNwgnZfg3VkcIJ2Xw"); + txtRecords.put( + "MJB42632KKOPGJ2KNY3GPKP66I.all.holesky.ethdisco.net", + "enr:-KO4QEBK_p981nqXnme4a2sYvHW2FOz_OwLwstllVRvE8w-dVnDBrHxVZg7XSQwseErDQpfUgYMnBPMzpsx_bDUui5eGAY09OSoRg2V0aMfGhJsZKtCAgmlkgnY0gmlwhLzWgw-Jc2VjcDI1NmsxoQM2fE-8xAAaBUMu7dwviv_y8osp0rYMaKtaGBnbUba_J4RzbmFwwIN0Y3CCdl2DdWRwgnZd"); + txtRecords.put( + "QXRV67JYJNOYJDPC5CTKUVG76M.all.holesky.ethdisco.net", + "enr:-KO4QN1MRyJdifSbU3Mg_GUI0_ApfQQ_BcPgDV0FXjVvk4iEWX6EgMjXkI62H1S0DFnyWeHnvr-caabqiKtlvbIBICyGAY5Cyclbg2V0aMfGhJsZKtCAgmlkgnY0gmlwhK9jhAaJc2VjcDI1NmsxoQL8tsMGtv3EuJfUdI8AzGBxYynVj2HlNoz4wi3COC4TTYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "QJM2RVJT7USPDSI5VVFGGDW6JM.all.holesky.ethdisco.net", + "enr:-Ji4QHWntDnLOi4S9GKmF4HY7WrnJuJGaIBrnM3Y75G-8EUpZExnUkaZJtlmzOeJs6WAcYsissKSybUG5rSEiad0s86ByoNldGjHxoSbGSrQgIJpZIJ2NIJpcISLY0UEiXNlY3AyNTZrMaED5hLZweFA_nvWEc-Q7Vxqf2lIYxScF-F4ORxs-rVyHZKDdGNwgtn5g3VkcILZ-Q"); + txtRecords.put( + "XTJ3PFTPBB3ATDDWAA6W7RRKMY.all.holesky.ethdisco.net", + "enrtree-branch:5AHKPN5NN5IHEH365X6OYZDHDE,AUQGHXRAP7J3AGI6TUOQVYTYLY,IDXHFSATDDENJYOYJC2TUSUYOY,GZVG4EONSAY6M7SMLJXDCFUF5Q,LHFTDS37XFOEXQKTWJGKWRLFWM,HTMBFBAIZBACDG3EZZ4JVANCAY,ZM7CMNKKBVIXY7WGMB7GHYUXMA,VNVQZHCW3CS7ELWR3ANLO7RFBA,PA4IN34FHBJOUK2MS7YRA3"); + txtRecords.put( + "5AHKPN5NN5IHEH365X6OYZDHDE.all.holesky.ethdisco.net", + "enr:-KO4QM-W42wfRYcTwaegJy3SfjlCkZsQU8LhGcluTdxtFjqXOwsEE5kUewkPMe7qIQpwb2MAo5xTeRpnijbdYTaG37-GAYtxMJdhg2V0aMfGhJsZKtCAgmlkgnY0gmlwhKh3IxmJc2VjcDI1NmsxoQNDrMpeyQI2aO_yoNP41RAf_BHtckptN1l2QIWxgDKOC4RzbmFwwIN0Y3CCdl-DdWRwgnZi"); + txtRecords.put( + "AUQGHXRAP7J3AGI6TUOQVYTYLY.all.holesky.ethdisco.net", + "enr:-KO4QGuiAZHlxH7T5XPcPpRTovca50B0pzDLkgJ-zaxkjT_PO6zsAWQezB-b53yAnqx01Jdj5tQWsvWoaNVfiFw3iw6GAY1nSgXzg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEp2jJuJc2VjcDI1NmsxoQMTiH7XteYOVAHiXAZoacUjo5JYLhQINeFMbPbEY9sA84RzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "IDXHFSATDDENJYOYJC2TUSUYOY.all.holesky.ethdisco.net", + "enr:-KO4QNWpVQT5pw-oUuoa7JDOVs1KheGaPRAR_fIarAflnc1yHawwEpptaB5JWkRXgh-wJtk2KlHE5zGICXefHD23Z4qGAY2-EYHHg2V0aMfGhJsZKtCAgmlkgnY0gmlwhK35GlyJc2VjcDI1NmsxoQPV0hDwmz1HaZ4pLm1hzee38NMugxA_7j3v6hbxP_LRCIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "GZVG4EONSAY6M7SMLJXDCFUF5Q.all.holesky.ethdisco.net", + "enr:-KO4QErAUNKF003oZAYn8UUocPIuq2MTpxHV3gHfrNkThYPbZ366m-mlvXezUX5JDA5iJE-LTWWEPh8JqjEDah5kVimGAYxA2nMcg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFtWv2Jc2VjcDI1NmsxoQMCgMWZQXkPyMKdwnzdBxQe6rcs25guZtULvfzDjhE71oRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "LHFTDS37XFOEXQKTWJGKWRLFWM.all.holesky.ethdisco.net", + "enr:-KO4QB0RN-qZSymbzZA-AlKT8213I60xJjVVwN-AWwjW9W9aLk-2aq8jW_jbgKnJY3c_wU26oBzYFBpOVcYKIV_nd16GAYwlWhtPg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMPJ9bKJc2VjcDI1NmsxoQKlc1OTSAanfHEsPtrKjZvkgQUuHFZon2GtnCRHlDRV7oRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "HTMBFBAIZBACDG3EZZ4JVANCAY.all.holesky.ethdisco.net", + "enr:-KO4QHHstlNCTI8xMnZ5mcIRvd8lfo9YoVeyv4pqvhtkxGI5PhYqDDqu1W5XqHWCMcTt8fpVgu9KojZHcyi7fhQhPdyGAY2_zUcvg2V0aMfGhJsZKtCAgmlkgnY0gmlwhL5czy-Jc2VjcDI1NmsxoQJpsTl5abUECWCfSno785tWBNPiVCJ7GG4eQTf2Cf1qBIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "ZM7CMNKKBVIXY7WGMB7GHYUXMA.all.holesky.ethdisco.net", + "enr:-KO4QNhl-I-sZux_gBOVLLbMdw8kMK9fSEBBNYukE2r95hDLJ9rOuhZgJO2dvNU_vCcuiqJNJHj8N3olsc7srxryuuCGAY0cB7Qsg2V0aMfGhP1PAWuAgmlkgnY0gmlwhJ7caOWJc2VjcDI1NmsxoQIyp37cpS7gvrKk0f3VxW4D9Jx7mOvSAQ6vOaQppsYp2oRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "VNVQZHCW3CS7ELWR3ANLO7RFBA.all.holesky.ethdisco.net", + "enr:-Ji4QBY7W0c9-rsM9r3yeBngIOe0LFJx8wew3ZatidckbRSzAbXbvc5ahmadpUcrPG2oDm9ziwxw7maQtzEs4J2AnWeBr4NldGjHxoSbGSrQgIJpZIJ2NIJpcISU--s8iXNlY3AyNTZrMaECwwCgmDrePUxVSsOZqgS0USzkXwzkqF0-QK7a59EsnrCDdGNwgnX7g3VkcIJ1-w"); + txtRecords.put( + "FTTHOFPRPIHMJNZA3VZUAW5TIM.all.holesky.ethdisco.net", + "enrtree-branch:V52J3WKNVGG56JFRKYDLSZREJY,ZCAW4EMBOL5EH4MSMW7FY6JMEE,2SA643JTHJ75ZVPFHAKLXI2WXQ,5RYAVZN3SSZZQ2KISUO7XDBPE4,366Y3UIIKK2G5CUS7CHNPZMPEM,7J7D5R4SH6LVDCFZ5ELOP46IP4,7QEPEVJFOCL5A63GZLZ4IZNMOI,OOWRWZXNDJVJUCDDOJFOY4YB2Y,H6XMM4W6QZEQDAPVXGHJSU"); + txtRecords.put( + "V52J3WKNVGG56JFRKYDLSZREJY.all.holesky.ethdisco.net", + "enr:-KO4QMC1Y7Fl44QyBr8KDFTyd1IQ_h0w67Igms87RDJeZJuXbc74aIVNkOeK0t3_zEJAT2spttSVu8tKDeV-cdiPE42GAY3KEqdCg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFsGDWJc2VjcDI1NmsxoQN24zvy5pQRtLA5_iKLgnTwXjI3T1KldFTOyp7Dk6dCRoRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "ZCAW4EMBOL5EH4MSMW7FY6JMEE.all.holesky.ethdisco.net", + "enr:-KO4QHc4CzkY6KkiTGR6hTovOeJ8VJuUtjkq2-UMXwUijiKvAZOFVS_Nn_GHMVZ8Ppsu31rSLYAEFRu0tMJ-LIDdSjOGAY2sYroCg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIrJyaeJc2VjcDI1NmsxoQN7HHah7WnGluJb0mqKelBJ8rpvKlBb9t7848BwJ4qau4RzbmFwwIN0Y3CCIB-DdWRwgiAf"); + txtRecords.put( + "2SA643JTHJ75ZVPFHAKLXI2WXQ.all.holesky.ethdisco.net", + "enr:-KO4QEPFY3aDBt5F3VQU5qcGZED8Xr-J9v1Fi4CcGpx7q_fydrJM8h0RqAXSdcJbr03b7ysPaOR4mNUTRou4kjPsu7yGAY4QKOX4g2V0aMfGhJsZKtCAgmlkgnY0gmlwhCO4xmuJc2VjcDI1NmsxoQKaOskToHERfimE_ei_VPRsh3fimBlYooVb80PVt0k-aIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "5RYAVZN3SSZZQ2KISUO7XDBPE4.all.holesky.ethdisco.net", + "enr:-KO4QGMFa1U36mwISUTSlJN8q2EKJSw4WHa6PAMrHkhL9-naVdAWmGuwfpel1E5_NBSnPWRsG8FzmmUH61iq8fodXcKGAYrbEkT3g2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFtb_iJc2VjcDI1NmsxoQJ62Txw9OKK7DTE_CZRC6tX_223dks-FcinnSL1uXUbTYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "366Y3UIIKK2G5CUS7CHNPZMPEM.all.holesky.ethdisco.net", + "enr:-KO4QK1ecw-CGrDDZ4YwFrhgqctD0tWMHKJhUVxsS4um3aUFe3yBHRtVL9uYKk16DurN1IdSKTOB1zNCvjBybjZ_KAqGAYtJ5U8wg2V0aMfGhJsZKtCAgmlkgnY0gmlwhA_MtDmJc2VjcDI1NmsxoQNXD7fj3sscyOKBiHYy14igj1vJYWdKYZH7n3T8qRpIcYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "7J7D5R4SH6LVDCFZ5ELOP46IP4.all.holesky.ethdisco.net", + "enr:-KO4QArnrdy9hfW8KHAwokQf-x0LbdoDQ4WoK0H2dXqhP4D-TflLF_Ywf2lh4ybXao8jjNSxC53MzmFPUI9A8k06XDqGAY2KrTRtg2V0aMfGhJsZKtCAgmlkgnY0gmlwhLWkyzGJc2VjcDI1NmsxoQKSAHhJOTtkBlasBECB1mrukuL6jqOEtxWkpH-PfDp8h4RzbmFwwIN0Y3CCequDdWRwgnqr"); + txtRecords.put( + "7QEPEVJFOCL5A63GZLZ4IZNMOI.all.holesky.ethdisco.net", + "enr:-KO4QNGh--C3ckyCMlUP4u86lt292CLHOQ3YIwk5Jqzz5x3OID_7BfRn5qN-ZBoBY80XOhFyHuo4eKDCIowQgYMvVY6GAY5S1aoSg2V0aMfGhJsZKtCAgmlkgnY0gmlwhFhjBqqJc2VjcDI1NmsxoQKZpCZSQgSun9pLzGrdfLboslJbD-JxtTsR4JF860eZYYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "OOWRWZXNDJVJUCDDOJFOY4YB2Y.all.holesky.ethdisco.net", + "enr:-KO4QBKCkXrhG-hN-IY-O03b5A4JHaweb5xuuSZP4OdJ4uSSZ7E1-yioWl_0WSF8ShOfNU_7wgSCIwBPg0d4RD4w4sOGAY4oBl8og2V0aMfGhJsZKtCAgmlkgnY0gmlwhFJB7YiJc2VjcDI1NmsxoQL-RfOTNeu0SmlCfWAVQaQBUXF0WDEUCyGXv1LWC2_xqoRzbmFwwIN0Y3CCequDdWRwgnqr"); + txtRecords.put( + "DAVKEW6RQ5YTYHKCJ2A2J5GVCI.all.holesky.ethdisco.net", + "enrtree-branch:6WAXAZL7FYKEJBZOPWIHCI4F6E,OL6KYVYI7SGS7U34FW3RFBA76A,FKSM7XWAT4KW3TDLRHWSXZPMKQ,5HSHHSS4QV3FKCNDETO7CEXMZM,CZ7ESLY5ZH4ULXKQ54XIFDCOUI,ZRVWX74UFHUWOPCC7LOPLYGIZA,FFM67SMD4N3SK4ZQTY2HOMU4F4,IPSUCB6CICZIW6SERKZL7FFTIM,7XE2DGFKCGOST6BJ46JPNK"); + txtRecords.put( + "6WAXAZL7FYKEJBZOPWIHCI4F6E.all.holesky.ethdisco.net", + "enr:-KO4QCagHy1Q7UED3AJfgVHxFKOn_DRS8UTUg0okE7fW8VTcDCW7wlzWufdWCtdUwHpnaf3EZpRn2YRuo9u4LS-Oh3qGAYrayS-wg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKI2Jc2VjcDI1NmsxoQJYC74L06jkGeWkmh1aSjeBYVvzDCWEvtbd111Vu8WiG4RzbmFwwIN0Y3CCdmqDdWRwgnZq"); + txtRecords.put( + "OL6KYVYI7SGS7U34FW3RFBA76A.all.holesky.ethdisco.net", + "enr:-KO4QIlf7XLihA1hw9d44SB0ENJ40RT1RmF2KrT9a2kRZfZoWPdO1jRi3GNnNSCLDxLo-aFc6uaGi3zxhCD8lg1aZgSGAYuoht2-g2V0aMfGhJsZKtCAgmlkgnY0gmlwhDNR0MWJc2VjcDI1NmsxoQKFvOHK4c-F1tHwdgsUa2yru5RrWyiVP9kRLnrw1kXcd4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "FKSM7XWAT4KW3TDLRHWSXZPMKQ.all.holesky.ethdisco.net", + "enr:-Ke4QNDn6wmnaZU1IYGb9lX8zb2QFUpa_yen8vFTnuXpW3_iah53AKPZX1D05HE23bw-UtR6zXOpCquB9XDdxvGhZrGGAY4oYEAkg2V0aMvKhMYaYJiEZRbqwIJpZIJ2NIJpcIQlPPlxiXNlY3AyNTZrMaEDiVRaEbnUMSPCtpxluUoBgOy3Loaa6SaVW9QK8BGTFJiEc25hcMCDdGNwgnZ9g3VkcIJ2fQ"); + txtRecords.put( + "5HSHHSS4QV3FKCNDETO7CEXMZM.all.holesky.ethdisco.net", + "enr:-KO4QAOJNWWAbN0bM51WFaFZimloFw1eBc7Pi3x7uMejmIToDZxBoAVtvdwTg3p6T0Mwmvuot6uT7PG2OjGxkk3P-iGGAY5cIO3Hg2V0aMfGhJsZKtCAgmlkgnY0gmlwhLAJA5uJc2VjcDI1NmsxoQPdRE5vPW9Wj-HRwzubnKAA-1n0p6CAOGLP-E-GzE9EnIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "CZ7ESLY5ZH4ULXKQ54XIFDCOUI.all.holesky.ethdisco.net", + "enr:-KO4QOQjO7c9J74CpllRngkrEXHLw-W8VqtJXxkLWPRIXC1CFL4TCjEM4ZfRW6MFlqXERaY3h9mztZXLAo1lf1DAS1qGAY2D0c0_g2V0aMfGhJsZKtCAgmlkgnY0gmlwhC06cBWJc2VjcDI1NmsxoQN8oyz6RCujPq6Hj9N8dBn3EeJ4A69HYeYnyLRiXLZ1UIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "ZRVWX74UFHUWOPCC7LOPLYGIZA.all.holesky.ethdisco.net", + "enr:-KO4QBfm8BEr7OIRv6UT1t5mhE2szefodQuIIBKMFnyvbky_K-wff-l6pqLwfLyUblqFNxqM6Xtcnari4ItzO9236DuGAY08Swa9g2V0aMfGhJsZKtCAgmlkgnY0gmlwhC7r5ZiJc2VjcDI1NmsxoQIH5RAmLOvYi50mYcMnglYJlz-EC0E2YsetPVUzvbs5BYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "FFM67SMD4N3SK4ZQTY2HOMU4F4.all.holesky.ethdisco.net", + "enr:-KO4QG4O1MBSzBHtShtqoXXeAycBqcDpuBK2BBwIuK_Qppz5OpJC6Ga3-qDEQJQOtbEYm0yXMhenb50Get_H6Bu9SbGGAY16lghqg2V0aMfGhJsZKtCAgmlkgnY0gmlwhA_rV76Jc2VjcDI1NmsxoQJ1vrJ4fwXiDAhSqIJwbELp2ktd9A0zRLws1li91-aBmoRzbmFwwIN0Y3CCdmCDdWRwgnZg"); + txtRecords.put( + "IPSUCB6CICZIW6SERKZL7FFTIM.all.holesky.ethdisco.net", + "enr:-KO4QD47Vx9bnFcvE6JNAhrLz6GGgPk9GaJtP95ogjSOFHxlcZjnrT5TQzW9Jyx99XO9rzhK6WSvgdFoA9_54QPQcUaGAYxBy9opg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIe1TLKJc2VjcDI1NmsxoQKMO6bMfiH5DnJNFZgSqUEi6uHNnH2_bMh7hwVXRWTMl4RzbmFwwIN0Y3CCdqmDdWRwgnap"); + txtRecords.put( + "TGKURK5IPVCPXS4QPS2KZJCP3Q.all.holesky.ethdisco.net", + "enrtree-branch:E4GGBOZ5L2UFPAKJIPLNBA2MKA,OF3GRGFZUAJTH3T2R2EXSYAZOU,D4GT5QUSIVDVPURCUOLE4WITZI,O6EQKGUJOO2BQGMADYZHOSZXLA,WRRS22MCKTZ4YS3ZLPUJEUAHUI,3A6T36TYZJBFLOHMJOJGL5SPXA,XPTWTOASNO4WEBXH74WCQ5EYTQ,2XMYGJOCRIHHMHAFVRH3OES5QY,FYJOHHN6LBARRNNLI6SK42"); + txtRecords.put( + "E4GGBOZ5L2UFPAKJIPLNBA2MKA.all.holesky.ethdisco.net", + "enr:-Ke4QD_Gz-YGWvzpAe-a1l2KHz2pxG_nPUYTcHQDN1Wuvkk7TrEDq-HKIXtA_mbPukz9qLLW5sfkJH5mYda9eDssvkSGAY1Mq2nIg2V0aMvKhMYaYJiEZRbqwIJpZIJ2NIJpcIQf3GvbiXNlY3AyNTZrMaECyosSeovqxsDXTua8c47z7EK--WogcD_FCwDX6tHvVQSEc25hcMCDdGNwgnZfg3VkcIJ2Xw"); + txtRecords.put( + "OF3GRGFZUAJTH3T2R2EXSYAZOU.all.holesky.ethdisco.net", + "enr:-KO4QDPbolwwOMTwV02zqyVgJFxko-HI8UWOT_K9sDcWgHG9fRDWkbSbTw_l3bcIA2-q3054lrJ9cbkHABlMZbQskUKGAY1y5VBWg2V0aMfGhJsZKtCAgmlkgnY0gmlwhKI3BbaJc2VjcDI1NmsxoQP9bM-ylBjwT9ujG09A__0to5F3Qw4QSvY4_vgyQ0A7j4RzbmFwwIN0Y3CCVTyDdWRwglU8"); + txtRecords.put( + "D4GT5QUSIVDVPURCUOLE4WITZI.all.holesky.ethdisco.net", + "enr:-J24QAgjCyFyNsDJHJwpo6uy0l7yqJD5r6WF58M1T7_PqsyKC0t6y1iTsQX4Crz6iv5Ijc8LqPZIC6vhA_F5GqkdkgSGAYtfbIIbg2V0aMfGhJsZKtCAgmlkgnY0gmlwhCvIu-OJc2VjcDI1NmsxoQKq0Xs_18RfgehL1rawMx2B3VMaH9d-YNCY5kmHuaCnqYN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "O6EQKGUJOO2BQGMADYZHOSZXLA.all.holesky.ethdisco.net", + "enr:-KO4QAg_or5YgVU8ScSgcgvNmLMISW0LA4L5GtRLxmyVUlGRR4GNuEZ9q_tKtZdAbLH5B-FN2ie8hp0U6P90d39xtyWGAY3d9Tplg2V0aMfGhJsZKtCAgmlkgnY0gmlwhAMRsEKJc2VjcDI1NmsxoQJ7STjsgZvt1OOj5krr2l2iAwlY5AGl-dgTziPZsCd9qIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "WRRS22MCKTZ4YS3ZLPUJEUAHUI.all.holesky.ethdisco.net", + "enr:-KO4QG6_DN_yg3WbcE7jc_tGXK9dLJPplfz0cbzCRwT8v5o6BBk_e_ER73cttVRyYkkvBDyw-akwTHYxjCe2Jp7OWuiGAYrbEiVVg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEV49OJc2VjcDI1NmsxoQKYHwvu1PazCFKpleCUgYvxialsHK6_iLwC_R2i3J70fYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "3A6T36TYZJBFLOHMJOJGL5SPXA.all.holesky.ethdisco.net", + "enr:-KO4QEVn1fwLQuOHSsRVS6bBIv7Nhr39Ze03wqUhwdgEn0IUeEB9TZAx6d9E-C0tBkQdEnbwld1YxEsB9gRY0Od13nGGAYzxMuKBg2V0aMfGhJsZKtCAgmlkgnY0gmlwhAW9up-Jc2VjcDI1NmsxoQKNkgm76tKKtumISB2-1oGEHv_mZtMVh7JGrSmvlX5qqYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "XPTWTOASNO4WEBXH74WCQ5EYTQ.all.holesky.ethdisco.net", + "enr:-KO4QEk3I50HU8omZLCYgSwXs1caXS-q06lC_GnJT30gbzlIEDWBldaoC83sg9aWTQ9DdkQStosOuYEb13gxTwp4tSKGAY5DOiDfg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEVX7SJc2VjcDI1NmsxoQPCijWe7yQtKeDfuj6WXiJZ6CZ88FGXKj6DGSWdq0E0qYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "2XMYGJOCRIHHMHAFVRH3OES5QY.all.holesky.ethdisco.net", + "enr:-KO4QAMrUvclx-EHZrO6x4K6fWTwaKh2zS5oWJzVU6oZuzZ2GG5xdQky8tIrpUUSKGB6XGvr8TvmiS7Dimvxx9Hb1OOGAYy06xQvg2V0aMfGhP1PAWuAgmlkgnY0gmlwhEFsSLGJc2VjcDI1NmsxoQJiz0q4ywyhHvmXBrWbFykzflq_J6cmLFIbepO9PLBwhoRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "RWRV55FT3DKDVGZK7AEU2DR77Y.all.holesky.ethdisco.net", + "enrtree-branch:ZV5CYUMJPF4XSTFHOG65622KEE,QDCV4SQTQAGHHA3DRA2TR4LNMY,MZGQKIQKIOFO3W2GJURJUXVYMQ,V5LTO36DCXU2JCCSSJ76E35OFU,NQHD4WNRRGE5JHHX6IDKGIF7KU,ELYMU6HPEMGLIYO2UO4YNUI7CQ,VN7EWW4RMUZM6KUOQ43GJ33DWM,J7ZIDKH7CKVIYY244QVU6RMPDM,QSK67XVHDPEQH7Q54HACQS"); + txtRecords.put( + "ZV5CYUMJPF4XSTFHOG65622KEE.all.holesky.ethdisco.net", + "enr:-KO4QDd2Ia2HHxIk_v5zyXOs6LIkR7a3wVUmipLZnQTJJMZtTNVY5PnRonxr_GAQ3nqG5R8f-eiyoYprxN1XjsfRlnyGAY2jAkZPg2V0aMfGhJsZKtCAgmlkgnY0gmlwhJ7cbM2Jc2VjcDI1NmsxoQLx82Y08wCtO72z_znVjHxPl3hBz1YBlPokUpsLFKRHJ4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "QDCV4SQTQAGHHA3DRA2TR4LNMY.all.holesky.ethdisco.net", + "enr:-KO4QFVXTftQWqwggnKZlVzFmnQz-U5jzIx37kQSltdvU5kuYQHT1mboaEBh92lS0BUvn-aBzLmHu7wX6hhs2xdGw-6GAYrk72-Hg2V0aMfGhJsZKtCAgmlkgnY0gmlwhCJSHduJc2VjcDI1NmsxoQNGPGIxN3h_R3TiUe0Ud2WazTigmsK028DXXuVLna9rtoRzbmFwwIN0Y3CCdTWDdWRwgnU1"); + txtRecords.put( + "MZGQKIQKIOFO3W2GJURJUXVYMQ.all.holesky.ethdisco.net", + "enr:-KO4QI7ESDG0rx7lEU_FFFFkALgNr9roSDrKDOzmEdxTHwsxKj5ozskjEjwFPrShowZbzTTYCp-NRhhw3uhcmKCYy0GGAY1lomwig2V0aMfGhJsZKtCAgmlkgnY0gmlwhCUbQPGJc2VjcDI1NmsxoQMV_yyKYF25xvgoJARC1swzb0S0llZJEmMLRHoINSR81YRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "V5LTO36DCXU2JCCSSJ76E35OFU.all.holesky.ethdisco.net", + "enr:-KO4QMLA20nV5gEPR5ugJWkqHSkU89wLsjSVlpdbjuXGueHRY_VJ7gfCBuPbnyxN3Y0rYJThJU7b7IAA1yBfcRx6Uo-GAYrbDOlPg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKGuJc2VjcDI1NmsxoQNZKgK-Lr-g81vUAH_bV24PDyOwNQaNPgAOBEA3qV3JxYRzbmFwwIN0Y3CCdnSDdWRwgnZ0"); + txtRecords.put( + "NQHD4WNRRGE5JHHX6IDKGIF7KU.all.holesky.ethdisco.net", + "enr:-KO4QMTq7zJT6fLS-LY7n3hWmcwJqxRJraGJcM60U9Cej2lRRE-u_KhOd9r712IBEOI39uiVQEXGdBZvc-dJIESUcOOGAY2dUsseg2V0aMfGhJsZKtCAgmlkgnY0gmlwhFD5eBSJc2VjcDI1NmsxoQO-UyE5ad7DaM-f7fZi4QEwjfxIR6HS-l-lTSlv4ZLdB4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "ELYMU6HPEMGLIYO2UO4YNUI7CQ.all.holesky.ethdisco.net", + "enr:-KO4QOWLzzuDHlwrEZhdOmeHEuFi87mX6iwJ_V820RsBL0mCLngiBaLbhumS1Q8zIc6YPzCYN07nigSMr-OXf19z3WSGAYra1TQYg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKFGJc2VjcDI1NmsxoQO4QASxyNUTDVIRkvEDQejetqJ4roUtGDD6U8CoI9wm-YRzbmFwwIN0Y3CCdnSDdWRwgnZ0"); + txtRecords.put( + "VN7EWW4RMUZM6KUOQ43GJ33DWM.all.holesky.ethdisco.net", + "enr:-KO4QI3OPqietyANJtQ9_RUOU6ELbwDgTn39MufjLJ9BEzkwTxEgDcgYekeisrchGWzrzTC8T_2zowbuQhZlTkqNXB2GAY39ItF5g2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFsBbiJc2VjcDI1NmsxoQLa2k_Jy-sjrZ-0305yX_F1ZxDNNfCKIImoL5tyDN3cEYRzbmFwwIN0Y3CCdl6DdWRwgnZe"); + txtRecords.put( + "J7ZIDKH7CKVIYY244QVU6RMPDM.all.holesky.ethdisco.net", + "enr:-KO4QN942TWYsHn5c2-vZhoeXwELvI2zVg2eHk6nzoyTN8Cwcb8n-4_cwQPIU5p7DsuOehtu-raiGxYm4vE_6WUhT6yGAY1ClkGgg2V0aMfGhJsZKtCAgmlkgnY0gmlwhJBbb1yJc2VjcDI1NmsxoQKemKJgEuvyzZofyTm82rbw716cyTA2hf6zSJ73kzl_ZIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "CTDMY3ALJA7FRKVS4MCTAJRRH4.all.holesky.ethdisco.net", + "enrtree-branch:UIGSNQMHERWJIZCP2OLUXSZ3KM,I56MJYJBMXTZZEPBQR6HWNAH7A,IZNO6JEFTYHQWWF3LJ2M66P7II"); + txtRecords.put( + "UIGSNQMHERWJIZCP2OLUXSZ3KM.all.holesky.ethdisco.net", + "enr:-KO4QEvn7KsE7UDJ5F3HwtSmGNSy0JSYDLfp9uJRtq5WfSsuOdkfq3V7GVSDo7IfCTXYAtQfRwT9q88II0sajv-m_CKGAYulV6JLg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMwQ9ISJc2VjcDI1NmsxoQNVsiSzpuxjNDgNToi4u48-5kCgvsjuoUsvlG9VrnEjhoRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "I56MJYJBMXTZZEPBQR6HWNAH7A.all.holesky.ethdisco.net", + "enr:-KO4QCTGqfxu7eEfe6M83e3bQLUgkKWGCdv6Ib5D0gCuQkkvbq1D0CN8UmHgpsddZNKHa2iBl3EIiGhaLBs_NKTxGQuGAYranLv7g2V0aMfGhJsZKtCAgmlkgnY0gmlwhCKH3xKJc2VjcDI1NmsxoQMHuEvotUinJNm_8Vz7412P1yKZ6r5iz9EcYj-9v3weRYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put( + "IZNO6JEFTYHQWWF3LJ2M66P7II.all.holesky.ethdisco.net", + "enr:-KO4QLmjLLs27xB9n9N2TNC32EhbxjdiR32rroemDty1dQJsBSZQOawi3-HGqWDRifXce49pv2WewtuzF5j54iiUfbaGAYrbEiX_g2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEV5riJc2VjcDI1NmsxoQO4pbsV79dSOLkBaOX5vcv6Amy9rH2xg1wW3OsKxVA7PoRzbmFwwIN0Y3CCdl-DdWRwgnZf"); + txtRecords.put("FDXN3SN67NA5DKA4J2GOK7BVQI.all.holesky.ethdisco.net", "enrtree-branch:"); + } + + /** Stops the mock DNS server. */ + public void stop() { + if (started.compareAndSet(true, false)) { + executorService.shutdown(); + try { + if (!executorService.awaitTermination(5, java.util.concurrent.TimeUnit.SECONDS)) { + executorService.shutdownNow(); + } + } catch (InterruptedException e) { + executorService.shutdownNow(); + Thread.currentThread().interrupt(); + } + } else { + LOG.warn("Mock DNS server is not running"); + } + } + + /** Starts the mock DNS server */ + public void start() { + if (started.compareAndSet(false, true)) { + startServer(); + } else { + LOG.warn("Mock DNS server is already running"); + } + } + + /** + * Mock server local port + * + * @return server port + */ + public int port() { + return dnsPort; + } + + private void startServer() { + executorService = Executors.newSingleThreadExecutor(); + executorService.execute( + () -> { + try (DatagramSocket socket = new DatagramSocket(0)) { + dnsPort = socket.getLocalPort(); + LOG.info("Mock DNS server started on port {}", dnsPort); + + while (started.get()) { + byte[] buffer = new byte[MAX_PACKET_SIZE]; + DatagramPacket packet = new DatagramPacket(buffer, buffer.length); + socket.receive(packet); + + String queryName = extractQueryName(buffer, packet.getLength()); + String txtRecord = txtRecords.get(queryName); + + byte[] response; + if (txtRecord != null) { + response = createTXTResponse(buffer, queryName, txtRecord); + } else { + response = createErrorResponse(buffer, queryName); + } + + final DatagramPacket responsePacket = + new DatagramPacket( + response, response.length, packet.getAddress(), packet.getPort()); + socket.send(responsePacket); + } + } catch (final IOException e) { + throw new UncheckedIOException("Unexpected IO Exception", e); + } + LOG.info("Mock DNS server stopped."); + }); + } + + private String extractQueryName(final byte[] buffer, final int length) { + StringBuilder queryName = new StringBuilder(); + int index = 12; // Skip the DNS header + + while (index < length) { + int labelLength = buffer[index] & 0xFF; + + if (labelLength == 0) { + break; + } + + index++; + + for (int i = 0; i < labelLength; i++) { + char c = (char) (buffer[index + i] & 0xFF); + queryName.append(c); + } + + index += labelLength; + + if (index < length && buffer[index] != 0) { + queryName.append("."); + } + } + + return queryName.toString(); + } + + private byte[] createTXTResponse( + final byte[] queryData, final String queryName, final String txtRecord) { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + DataOutputStream dataOutputStream = new DataOutputStream(outputStream)) { + + // Write DNS header + dataOutputStream.writeShort(getQueryId(queryData)); // Identifier (based on query) + dataOutputStream.writeShort(0x8180); // Flags (Standard query response, No error) + dataOutputStream.writeShort(1); // Questions count + dataOutputStream.writeShort(1); // Answers count + dataOutputStream.writeShort(0); // Authority RRs count + dataOutputStream.writeShort(0); // Additional RRs count + + // Write query name + Iterable queryLabels = Splitter.on(".").split(queryName); + for (String label : queryLabels) { + dataOutputStream.writeByte(label.length()); + dataOutputStream.writeBytes(label); + } + dataOutputStream.writeByte(0); // End of query name + + // Write query type and class + dataOutputStream.writeShort(16); // Type (TXT) + dataOutputStream.writeShort(1); // Class (IN) + + // Write answer + for (String label : queryLabels) { + dataOutputStream.writeByte(label.length()); + dataOutputStream.writeBytes(label.toLowerCase(Locale.ROOT)); + } + dataOutputStream.writeByte(0); // End of answer name + + dataOutputStream.writeShort(16); // Type (TXT) + dataOutputStream.writeShort(1); // Class (IN) + dataOutputStream.writeInt(60); // TTL (60 seconds) + + byte[] txtRecordBytes = txtRecord.getBytes(StandardCharsets.UTF_8); + dataOutputStream.writeShort(txtRecordBytes.length + 1); // Data length + dataOutputStream.writeByte(txtRecordBytes.length); // TXT record length + dataOutputStream.write(txtRecordBytes); // TXT record data + + dataOutputStream.flush(); + return outputStream.toByteArray(); + } catch (final IOException e) { + throw new UncheckedIOException("Unexpected IO Exception", e); + } + } + + private byte[] createErrorResponse(final byte[] queryData, final String queryName) { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + DataOutputStream dataOutputStream = new DataOutputStream(outputStream)) { + + // Write DNS header + dataOutputStream.writeShort(getQueryId(queryData)); // Identifier (random) + dataOutputStream.writeShort(0x8183); // Flags (Standard query response, NXDOMAIN error) + dataOutputStream.writeShort(1); // Questions count + dataOutputStream.writeShort(0); // Answers count + dataOutputStream.writeShort(0); // Authority RRs count + dataOutputStream.writeShort(0); // Additional RRs count + + // Write query name + for (String label : Splitter.on(".").split(queryName)) { + dataOutputStream.writeByte(label.length()); + dataOutputStream.writeBytes(label); + } + dataOutputStream.writeByte(0); // End of query name + + // Write query type and class + dataOutputStream.writeShort(16); // Type (TXT) + dataOutputStream.writeShort(1); // Class (IN) + + dataOutputStream.flush(); + return outputStream.toByteArray(); + } catch (final IOException e) { + throw new UncheckedIOException("Unexpected IO Exception", e); + } + } + + private short getQueryId(final byte[] queryData) { + return (short) ((queryData[0] & 0xff) << 8 | (queryData[1] & 0xff)); + } +} diff --git a/ethereum/p2p/src/test/resources/log4j2-test.xml b/ethereum/p2p/src/test/resources/log4j2-test.xml new file mode 100644 index 00000000000..20ef3c1c680 --- /dev/null +++ b/ethereum/p2p/src/test/resources/log4j2-test.xml @@ -0,0 +1,19 @@ + + + + INFO + + + + + + + + + + + + + + + From 107bf0244d68776d2ffc003ad61ee657468592dc Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Tue, 28 May 2024 09:51:24 +1000 Subject: [PATCH 13/26] add final keyword Signed-off-by: Usman Saleem --- .../besu/ethereum/p2p/discovery/dns/DNSResolver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java index ee39a0a0289..d42304ed043 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java @@ -68,7 +68,7 @@ public DNSResolver( dnsClient = vertx.createDnsClient(dnsClientOptions); } - private static DnsClientOptions buildDnsClientOptions(String server) { + private static DnsClientOptions buildDnsClientOptions(final String server) { final List hostPort = Splitter.on(":").splitToList(server); final DnsClientOptions dnsClientOptions = new DnsClientOptions(); dnsClientOptions.setHost(hostPort.get(0)); From 2ab08bf427640f818f871e426753dba46309e24b Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Tue, 28 May 2024 09:53:32 +1000 Subject: [PATCH 14/26] changelog: Update changelog Signed-off-by: Usman Saleem --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db5924f066a..47d09fdbe49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ - Default bonsai to use full-flat db and code-storage-by-code-hash [#6984](https://github.com/hyperledger/besu/pull/6894) - New RPC methods miner_setExtraData and miner_getExtraData [#7078](https://github.com/hyperledger/besu/pull/7078) - Disconnect peers that have multiple discovery ports since they give us bad neighbours [#7089](https://github.com/hyperledger/besu/pull/7089) -- Port Tuweni DNS discovery into Besu p22 module. [#7129](https://github.com/hyperledger/besu/pull/7129) +- Port Tuweni dns-discovery into Besu. [#7129](https://github.com/hyperledger/besu/pull/7129) ### Known Issues - [Frequency: occasional < 10%] Chain download halt. Only affects new syncs (new nodes syncing from scratch). Symptom: Block import halts, despite having a full set of peers and world state downloading finishing. Generally restarting besu will resolve the issue. We are tracking this in [#6884](https://github.com/hyperledger/besu/pull/6884) From 0ad16c1ad4ba1c9211ef145e4a555f3301197a87 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Tue, 28 May 2024 10:17:07 +1000 Subject: [PATCH 15/26] codefix: spotless apply Signed-off-by: Usman Saleem --- ethereum/p2p/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/ethereum/p2p/build.gradle b/ethereum/p2p/build.gradle index a5627cb13f7..21bfdc6bf54 100644 --- a/ethereum/p2p/build.gradle +++ b/ethereum/p2p/build.gradle @@ -87,5 +87,4 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter' testImplementation 'org.mockito:mockito-core' testImplementation 'org.mockito:mockito-junit-jupiter' - } From b521d42a4b4ad554f3e578a3825ebee6cdf89eb9 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Tue, 28 May 2024 11:30:47 +1000 Subject: [PATCH 16/26] revert gradle verification metadata Signed-off-by: Usman Saleem --- gradle/verification-metadata.xml | 42 -------------------------------- 1 file changed, 42 deletions(-) diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 5b79d92acff..7ad666233f6 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -1414,17 +1414,6 @@ - - - - - - - - - - - @@ -1693,11 +1682,6 @@ - - - - - @@ -2707,11 +2691,6 @@ - - - - - @@ -2738,14 +2717,6 @@ - - - - - - - - @@ -2754,24 +2725,11 @@ - - - - - - - - - - - - - From e54cc02d062a2a8a6b126e4a2a5d539ce5a2d48e Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Tue, 28 May 2024 11:35:48 +1000 Subject: [PATCH 17/26] throw illegalstateexception if dnsdaemon is not started as a vertx verticle Signed-off-by: Usman Saleem --- .../besu/ethereum/p2p/discovery/dns/DNSDaemon.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java index 18cfe5b7879..c3a08c4c473 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java @@ -69,7 +69,11 @@ public DNSDaemon( /** Starts the DNSDaemon. */ @Override public void start() { - LOG.info("Starting DNSDaemon for {}, using {} DNS host", enrLink, dnsServer.orElse("default")); + if (vertx == null) { + throw new IllegalStateException("DNSDaemon must be deployed as a vertx verticle."); + } + + LOG.info("Starting DNSDaemon for {}, using {} DNS host.", enrLink, dnsServer.orElse("default")); this.dnsResolver = new DNSResolver(vertx, enrLink, seq, dnsServer); if (delay > 0) { periodicTaskId = Optional.of(vertx.setPeriodic(initialDelay, delay, this::refreshENRRecords)); From e77fa1e0f7112a6d841a6d534f6336eff3372b9c Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 30 May 2024 20:54:18 +1000 Subject: [PATCH 18/26] codefix: remove unnecessary this prefix Signed-off-by: Usman Saleem --- .../hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java index c3a08c4c473..d17c846be79 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java @@ -74,7 +74,7 @@ public void start() { } LOG.info("Starting DNSDaemon for {}, using {} DNS host.", enrLink, dnsServer.orElse("default")); - this.dnsResolver = new DNSResolver(vertx, enrLink, seq, dnsServer); + dnsResolver = new DNSResolver(vertx, enrLink, seq, dnsServer); if (delay > 0) { periodicTaskId = Optional.of(vertx.setPeriodic(initialDelay, delay, this::refreshENRRecords)); } else { From 400f56023d63e35bd2ab3bfeb42913964029b346 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 30 May 2024 21:01:35 +1000 Subject: [PATCH 19/26] codefix: decode public key in constructor Signed-off-by: Usman Saleem --- .../ethereum/p2p/discovery/dns/DNSEntry.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntry.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntry.java index 5d49f11d2ea..bb85a3612f3 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntry.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntry.java @@ -232,7 +232,8 @@ public String toString() { /** Class representing an ENR Tree link */ class ENRTreeLink implements DNSEntry { private final String domainName; - private final String pubKey; + private final String encodedPubKey; + private final SECP256K1.PublicKey pubKey; /** * Creates a new ENRTreeLink @@ -242,18 +243,23 @@ class ENRTreeLink implements DNSEntry { public ENRTreeLink(final String enrTreeLink) { final URI uri = URI.create(enrTreeLink); this.domainName = uri.getHost(); - this.pubKey = uri.getUserInfo(); + this.encodedPubKey = uri.getUserInfo(); + this.pubKey = fromBase32(encodedPubKey); + } + + private static SECP256K1.PublicKey fromBase32(final String base32) { + final byte[] keyBytes = Base32.decodeBytes(base32); + final ECPoint ecPoint = SECP256K1.Parameters.CURVE.getCurve().decodePoint(keyBytes); + return SECP256K1.PublicKey.fromBytes(Bytes.wrap(ecPoint.getEncoded(false)).slice(1)); } /** - * Derive public key from the link + * Decoded SECP256K1 public key. * - * @return the public key + * @return derived SECP256K1.PublicKey */ public SECP256K1.PublicKey publicKey() { - final byte[] keyBytes = Base32.decodeBytes(pubKey); - final ECPoint ecPoint = SECP256K1.Parameters.CURVE.getCurve().decodePoint(keyBytes); - return SECP256K1.PublicKey.fromBytes(Bytes.wrap(ecPoint.getEncoded(false)).slice(1)); + return pubKey; } /** @@ -267,7 +273,7 @@ public String domainName() { @Override public String toString() { - return String.format("enrtree://%s@%s", pubKey, domainName); + return String.format("enrtree://%s@%s", encodedPubKey, domainName); } } } From dd2f7411e61b734b9f427e2b583f8e323e3e1725 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 30 May 2024 21:03:20 +1000 Subject: [PATCH 20/26] codefix: Remove unnecessary files Signed-off-by: Usman Saleem --- .../p2p/discovery/dns/DnsPortSplitTest.java | 38 ------------------- .../p2p/src/test/resources/log4j2-test.xml | 19 ---------- 2 files changed, 57 deletions(-) delete mode 100644 ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DnsPortSplitTest.java delete mode 100644 ethereum/p2p/src/test/resources/log4j2-test.xml diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DnsPortSplitTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DnsPortSplitTest.java deleted file mode 100644 index 945b40c04e9..00000000000 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DnsPortSplitTest.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.p2p.discovery.dns; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.List; - -import com.google.common.base.Splitter; -import org.junit.jupiter.api.Test; - -public class DnsPortSplitTest { - @Test - void hostWithoutPortShouldBeParsed() { - final String host = "localhost"; - final List hostPort = Splitter.on(":").splitToList(host); - assertThat(hostPort).hasSize(1).containsExactly("localhost"); - } - - @Test - void hostWithPortShouldBeParsed() { - final String host = "localhost:52"; - final List hostPort = Splitter.on(":").splitToList(host); - assertThat(hostPort).hasSize(2).containsExactly("localhost", "52"); - } -} diff --git a/ethereum/p2p/src/test/resources/log4j2-test.xml b/ethereum/p2p/src/test/resources/log4j2-test.xml deleted file mode 100644 index 20ef3c1c680..00000000000 --- a/ethereum/p2p/src/test/resources/log4j2-test.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - INFO - - - - - - - - - - - - - - - From 16507f606b92d4ce985867b6597dabaa4b3e52af Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 30 May 2024 21:08:27 +1000 Subject: [PATCH 21/26] codefix: Fix LOG class reference Signed-off-by: Usman Saleem --- .../besu/ethereum/p2p/discovery/dns/MockDNSServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/MockDNSServer.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/MockDNSServer.java index 6c039649619..6f8ce9e3b75 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/MockDNSServer.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/MockDNSServer.java @@ -34,7 +34,7 @@ /** A Mock DNS Server that returns fixed TXT entries. */ public class MockDNSServer { - private static final Logger LOG = LoggerFactory.getLogger(DNSResolver.class); + private static final Logger LOG = LoggerFactory.getLogger(MockDNSServer.class); private static final int MAX_PACKET_SIZE = 512; private final Map txtRecords = new HashMap<>(); From 9750fff1abaf9a99d13670212a56a0c23da62690 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 30 May 2024 21:14:07 +1000 Subject: [PATCH 22/26] codefix: Reduce initial delay and recurring delay in dnsdaemontest Signed-off-by: Usman Saleem --- .../besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java index d9b6820b2c2..695165cdfec 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java @@ -104,8 +104,8 @@ void testDNSDaemonPeriodic(final Vertx vertx, final VertxTestContext testContext checkpoint.flag(); }, 0, - 100, // starts the lookup after 100 ms - 5000, // 5 seconds delay on the second lookup + 1, // initial delay + 50, // second lookup after 50 ms (due to Mock DNS server, we are very quick). "localhost:" + mockDNSServer.port()); final DeploymentOptions options = From 8cf02d72b09fb5964d4e8ec3b9f5dad672dfef80 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 30 May 2024 21:40:06 +1000 Subject: [PATCH 23/26] codefix: Read dns txt entries from a json file in mock dns server Signed-off-by: Usman Saleem --- .../p2p/discovery/dns/MockDNSServer.java | 423 +----------------- .../resources/discovery/dns/dns-records.json | 137 ++++++ 2 files changed, 154 insertions(+), 406 deletions(-) create mode 100644 ethereum/p2p/src/test/resources/discovery/dns/dns-records.json diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/MockDNSServer.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/MockDNSServer.java index 6f8ce9e3b75..d845c2654f6 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/MockDNSServer.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/MockDNSServer.java @@ -14,13 +14,14 @@ */ package org.hyperledger.besu.ethereum.p2p.discovery.dns; +import static java.nio.charset.StandardCharsets.UTF_8; + import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.UncheckedIOException; import java.net.DatagramPacket; import java.net.DatagramSocket; -import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -28,7 +29,10 @@ import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Splitter; +import com.google.common.io.Resources; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,410 +48,17 @@ public class MockDNSServer { /** Create an instance of MockDNSServer. Add TXT records that can be served by this server. */ public MockDNSServer() { - // snapshot on 2024/05/27 - txtRecords.put( - "all.holesky.ethdisco.net", - "enrtree-root:v1 e=QXOF2GWVHBKMKW57Y2KSKWYNFQ l=FDXN3SN67NA5DKA4J2GOK7BVQI seq=932 sig=DuA35BkYo9-FBwJ6MPxdNnYfcGMSGunAKUyfNN2gYQhYDBCPFZkr_cfe40Wspl2Vl76w6Ccs-B8ZrXpI_YymrAA"); - txtRecords.put( - "QXOF2GWVHBKMKW57Y2KSKWYNFQ.all.holesky.ethdisco.net", - "enrtree-branch:A63OP4WTCB3HGDZE4NGDEID6Z4,PL27G47LAASBPPAMXUDIJ3OCRQ"); - txtRecords.put( - "A63OP4WTCB3HGDZE4NGDEID6Z4.all.holesky.ethdisco.net", - "enrtree-branch:QXKEJG4XZEQSXNUY7JJYCATPEI,HIDVATDVB36L2MASAWA7SBJAII,CHFGCI2RQS3XFN2MKFP6G2ZM4U,GSONRYZILMGUJEN3PYQXYD6GYQ,OSHAABXYJSRWW35VOMYHZUKJXU,RUJF27NEYDBNQAFMI6K6SKDVRY,TXAFU343ACQLSVIMT5LYG3W2AE,R6EQA5KQEQM77JJXB4BHHTDF6Y,CF25LCQ452FBTR24CZU4C2"); - txtRecords.put( - "QXKEJG4XZEQSXNUY7JJYCATPEI.all.holesky.ethdisco.net", - "enrtree-branch:FEORTRPBZJSNRB3XCLK7KCMACQ,2M2GRZYAFKAVHHU5HTLUHPYHCM,KEC4722NVHE3KX3IYNZC34C7NY,NZCRGKUE7ZXSIS4MIQ4WUS76BA,3PYSNKXWWSRBWV377EFNYINHSA,C7SDD5OPASV7B2XUOXF5NLBBKU,QY2XP6FTX7QKFNP6TNZ2MOHZKQ,76OAILAEE52CRVMYFBIBMSEUXE,2QHFEUHLG6Q35B5LUS5BF3"); - txtRecords.put( - "FEORTRPBZJSNRB3XCLK7KCMACQ.all.holesky.ethdisco.net", - "enr:-KO4QDO4oH5cjhncisJSk1SyGmwJ5VFjNetXw4OSSqDKc0NMDGXYYl6wUjbhcvzADYDpE1Br3lRG-1xV6iA1OAyXSAqGAY1W3CQQg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEVfkOJc2VjcDI1NmsxoQKkjEwDL0wuG0AH3RWw1wRrYHdPa8OOL1Ko4DYcZeQjQoRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "2M2GRZYAFKAVHHU5HTLUHPYHCM.all.holesky.ethdisco.net", - "enr:-KO4QIPWnAyt-tyaS1cnMwEJaFdvDd-MpwEcVv26cfaZ3Ffkc4WCpHqzPmd_SifEsxQf7ontOz9a1EPjbxlsu_V1XZqGAY1UHtm_g2V0aMfGhJsZKtCAgmlkgnY0gmlwhCU85MWJc2VjcDI1NmsxoQNyzc5NiamRt8y5TQ9Qj-EZAYeCb7ZaclF5sOdPrErpbIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "KEC4722NVHE3KX3IYNZC34C7NY.all.holesky.ethdisco.net", - "enr:-KO4QMjtE87kVQpcuRPXV31w7fsvyit4Fw995wyZ_h6uK8atWGOhXpsi3xXsBQLgJFBWmNHgTw-V4JvZNFZkFHLU_8mGAY5hRoXMg2V0aMfGhJsZKtCAgmlkgnY0gmlwhC1Mp4CJc2VjcDI1NmsxoQPETNdXN5Cshfr8-rIIMev6E_MGqD-jzqcXW2yfzT3C54RzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "NZCRGKUE7ZXSIS4MIQ4WUS76BA.all.holesky.ethdisco.net", - "enr:-Je4QDqwqlm7UBywmVlR5UkkrQrg5B3UpkFexP7ucg4RAsdlMiJ4J1S5jT8jp6je6igMZ3OOnggdpd6l7QtQeNBICycDg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFs6LuJc2VjcDI1NmsxoQMgMreR2XspGJphg3fToxGKcMwWPE3e0gyxcqiQNLQXNIN0Y3CCdTuDdWRwgnU7"); - txtRecords.put( - "3PYSNKXWWSRBWV377EFNYINHSA.all.holesky.ethdisco.net", - "enr:-KO4QODvXk9lfhLNmdyttodPJQOIYQM36Rl9OZsFBZ4vMjaJcoJUXxTQCMqGLNTWmFh-1oEy0XoYKgPy4tywLZZcB8CGAYsolPILg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIe1OV6Jc2VjcDI1NmsxoQPj1f0OOW-g_vRGcihewV9-kcsQZdwXBHy4r8vwinurzoRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "C7SDD5OPASV7B2XUOXF5NLBBKU.all.holesky.ethdisco.net", - "enr:-KO4QLo95TIKo_axZA9xafYgl9jQ2ZTDFCQ29znjUwp-6zPUe6o8L9NO3X3n3uMZH8bFGnRmoIhyHLxNoOp2BU6O7xGGAYwoJEiug2V0aMfGhJsZKtCAgmlkgnY0gmlwhKEjElWJc2VjcDI1NmsxoQPIffRacW7k1QukSBfJ8leopO1M5wOv89G06m0SAQDB8oRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "QY2XP6FTX7QKFNP6TNZ2MOHZKQ.all.holesky.ethdisco.net", - "enr:-KO4QFsIg8OnqK5HJhhCaEuUTJ7RkLcTAXnUOOcaK63Ra2Kya2HsYSRgM1jRyr9utt3ib83q4fsU_IOg7kuR1jf2u2SGAYray1Tfg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKI2Jc2VjcDI1NmsxoQJiD81UDxWESi3paPaopKxnoF5vf0GAxlroaPr6bSIykIRzbmFwwIN0Y3CCdn6DdWRwgnZ-"); - txtRecords.put( - "76OAILAEE52CRVMYFBIBMSEUXE.all.holesky.ethdisco.net", - "enr:-KO4QDRyM88zIGJ2P0Ya1W8fucbp8kRTbVzV8k_2eawB0W3ZHMQ3o0HlbXVw3j1rMgN167LmsaMt1iO015HMVKGT2GqGAY0rjdqhg2V0aMfGhP1PAWuAgmlkgnY0gmlwhKFhcJuJc2VjcDI1NmsxoQJ2emahe6-2fq_hQqxm99rgYi4TSzQ1ky4utO3Tcpe-yYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "HIDVATDVB36L2MASAWA7SBJAII.all.holesky.ethdisco.net", - "enrtree-branch:N7HAL5M6HNZBGTWM3LWFDRX4WU,PX6K4ZETH5UX56IKHKL5TNOOMU,I2Q5U7BDYSM7O3O2IKIRL3KTZY,O4SSPZG3DFA7PHZ5L42DVOPWDM,Z5DOK3IB5X3IBOVVIK5MQCDYNA,ZJOONJUDYAQ53XOJU4KG5W4ATM,NLYAF5XVTTQUOMK4LYUF3FXNPE,XPX2T64BHDYLVGT5UC4GVIQIVY,AHB67R6KGAKWRSBL7CRAO3"); - txtRecords.put( - "N7HAL5M6HNZBGTWM3LWFDRX4WU.all.holesky.ethdisco.net", - "enr:-Je4QI2f2MAXFFi0Q3--GhXVZwNP-jI-G6XwmiDXxT-iobTTKOBJCyWUPk5WSoTtRS2ABqJmTAK8jflXBWLHuY7AueRFg2V0aMfGhP1PAWuAgmlkgnY0gmlwhDmAXJmJc2VjcDI1NmsxoQIc4YD6HsNIoj2HVJJDzr3cSfNtcEBRLtk5kgwXNH0q8YN0Y3CCXoeDdWRwgl6H"); - txtRecords.put( - "PX6K4ZETH5UX56IKHKL5TNOOMU.all.holesky.ethdisco.net", - "enr:-KO4QJqslvj0RYuX2CI61yCK7VuyY9Ik3c5EHtpkKehriXyqchhCDMx4sRuewcCAiWO_frsUevV9GXMPnsO2nzCrgFGGAY25poLHg2V0aMfGhJsZKtCAgmlkgnY0gmlwhKfrsZeJc2VjcDI1NmsxoQOe9l9K5UW0RAuQTH1q6CwG_UzbJyKLGSt4lUt5_tVmyIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "I2Q5U7BDYSM7O3O2IKIRL3KTZY.all.holesky.ethdisco.net", - "enr:-KO4QIvQh_txoF6gwXTRsMzzvHPdwQrkCHQYt4egwTWqUNQiMQh1yJ71U8UXs8b9pP9R1mUDTaiQ7-e0eB2Zbvouyb-GAYvSz8f0g2V0aMfGhP1PAWuAgmlkgnY0gmlwhJVmk6SJc2VjcDI1NmsxoQIuT88b-jLJBI40hV4VY3YeJnmYT65v2QPjyzuNG5dyF4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "O4SSPZG3DFA7PHZ5L42DVOPWDM.all.holesky.ethdisco.net", - "enr:-KO4QMrNy68H-dYJbPM7snJ1UrV-TPXA012sdna6WktkfksMED9fE3g-kzp7gBgOFEdFi1KIiEv5MXXAoOt1KOMVc1eGAYw66STEg2V0aMfGhJsZKtCAgmlkgnY0gmlwhF_ZxOCJc2VjcDI1NmsxoQJUhK-aKtl-Gc3ZnfF2k7oLFXmpGCHEi1_fBxAEfc1P54RzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "Z5DOK3IB5X3IBOVVIK5MQCDYNA.all.holesky.ethdisco.net", - "enr:-KO4QJjEv14HV0BgdPwIa0iO-xrrwoi9k9LJRw3utIUVib5dF6s_b3y8z0NP2VyUGsJDFOwuVQq6OC6hb5_hFUqnf1yGAY12fbSog2V0aMfGhJsZKtCAgmlkgnY0gmlwhCU8-mOJc2VjcDI1NmsxoQIT2maaVlQBw4oD4loM1Q7gygn42pTQn1Sydo74LtZAZYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "ZJOONJUDYAQ53XOJU4KG5W4ATM.all.holesky.ethdisco.net", - "enr:-KO4QBGQGqvIoSv3C7d-bCiMst5GGEvIQaQ5pAHs5On0MeNfcVjcZgd72l2UUoqf13HoKHUnVG37cmHw-11H4Y59F6iGAYrbEhVng2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFtO36Jc2VjcDI1NmsxoQJ3QRuoMLUn6djLfYCFTKSV8kUWRQTOethOOhLDW6ezdYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "NLYAF5XVTTQUOMK4LYUF3FXNPE.all.holesky.ethdisco.net", - "enr:-KO4QMiia-4vyLiHQQhxlbmCKdOTRqOiZin-BdAdi18LxNqqHEiuIQQ-F1dZu8sKi63vEk9zwr5gfMnxXQZCThEaiDSGAY2UmHG4g2V0aMfGhJsZKtCAgmlkgnY0gmlwhMb0_ESJc2VjcDI1NmsxoQLb1ZwjPQw7AjCvlHQpt9bmePVD85rbHbnFZ0naOB1RNYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "XPX2T64BHDYLVGT5UC4GVIQIVY.all.holesky.ethdisco.net", - "enr:-KO4QMlMC7rvmM9tvHXvwo2Vj5x5FePlyvH_6lBSCgZHN8HcWqtppk1peQlK9Ge79ma4j3gx_zX0hgJvprIy1ehjXXCGAY3Mer4jg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIe1FCCJc2VjcDI1NmsxoQL3IIF3eENfSsr5tih2ebcm3dWL-otLdZYqUvfJ-5_9U4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "CHFGCI2RQS3XFN2MKFP6G2ZM4U.all.holesky.ethdisco.net", - "enrtree-branch:XIOWDM2BTMANILG2M4LCMO7W3M,4LPHENNNENCUT7P3MWTB5IFRTY,5NQ4MKTYBPBMTGUT5IKUAXEGBE,CVK4DBIVPD2JPJOPQJQX77ZAWU,WLME2JINTIQACAMD5WZUQ4PRJI,DQYYUE7KVEXGEYBH4V2QHI6P7M,45V6KFI4NL43JFTJHL77KP6MQA,QDMF4HIR2UEXRJVOX4UJPJTPKA,BMVEIV6PNSN6RK5XEU4R4K"); - txtRecords.put( - "XIOWDM2BTMANILG2M4LCMO7W3M.all.holesky.ethdisco.net", - "enr:-Je4QCvStdCVA-jcYsFoXmDXjXlp-Cd_9sSMdi9Y1LM3tGH2C3k49yrWy-Y2Jg93ikASYljvD9qVUES0T9q5htcbL60ag2V0aMfGhJsZKtCAgmlkgnY0gmlwhLkIa-2Jc2VjcDI1NmsxoQKcjBIYl8vFVYVBs5trEl-Zn3IE7u1ZiiV986p0QCrAL4N0Y3CCdl2DdWRwgnZd"); - txtRecords.put( - "4LPHENNNENCUT7P3MWTB5IFRTY.all.holesky.ethdisco.net", - "enr:-KO4QF8nbKqJkOeYlF-akOVBYzusVwSjI_ra_t7TmM52mEisQLCwNAjujSSc9-6ECErhss-gVTgBDzhY5QmVcF0PuZOGAY3Li2SUg2V0aMfGhJsZKtCAgmlkgnY0gmlwhCL__BOJc2VjcDI1NmsxoQLHimHBtxJxch5bc20e_O8OdeN3UQx6TQQWqaYfyi66OYRzbmFwwIN0Y3CCdW-DdWRwgnVv"); - txtRecords.put( - "5NQ4MKTYBPBMTGUT5IKUAXEGBE.all.holesky.ethdisco.net", - "enr:-Ke4QLrsveUYt2tacm5EZETFc1F3EyvNYfRRkRhljyeLMIzccRlPI1kKmBWuELQs5iAIRZgv92P3Fxx_zJ3xyUbN3muGAY0x7p4Ng2V0aMvKhMYaYJiEZRbqwIJpZIJ2NIJpcIRU94PXiXNlY3AyNTZrMaEDBxF5W6guB9qZoR-c_zuDUE1UHyaH0FMjJKHC9Jq4ji6Ec25hcMCDdGNwgnZfg3VkcIJ2Xw"); - txtRecords.put( - "CVK4DBIVPD2JPJOPQJQX77ZAWU.all.holesky.ethdisco.net", - "enr:-KO4QH3RGFoO4KTuq8gxJC_mjnPBqNaDvoX_xsuYbDOqvJA4CPJs6nifZuqE2sTB3O-kHvEI0dbFJ87FoWkKhgmXYuOGAYrbEhVBg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEV7HyJc2VjcDI1NmsxoQM1AMXary_Hiw-yesOw24q7GwMH_DmNHHitFPU46foJ24RzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "WLME2JINTIQACAMD5WZUQ4PRJI.all.holesky.ethdisco.net", - "enr:-J24QIMYTLuDOO1F5w8AL_WGXU6CZmzi05W35Rz8PuVT8b6vUqmUatifQtzuL2Q7REuRY5kYNurUbLHscGx3j1zjTmmGAYrbbtBig2V0aMfGhJsZKtCAgmlkgnY0gmlwhETp6VKJc2VjcDI1NmsxoQKrwy6w-9JGp3dagVeXMZEd3iZGff-rEVTqd4gmkhBRBIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "DQYYUE7KVEXGEYBH4V2QHI6P7M.all.holesky.ethdisco.net", - "enr:-KO4QBhk9j1k2tfVrwV4yVRS9jn2zwS9KKADlhqvMsZsqdXFZiz0s-vCAnqtkDfszY9peGDBiWulLdNe6KFuvqgxgy6GAYtHL5rAg2V0aMfGhJsZKtCAgmlkgnY0gmlwhDmBAQqJc2VjcDI1NmsxoQLXrL6FtBz248tpC2_DxJs2HsvtHdSoNsZIBwth_MMu94RzbmFwwIN0Y3CCdmqDdWRwgnZq"); - txtRecords.put( - "45V6KFI4NL43JFTJHL77KP6MQA.all.holesky.ethdisco.net", - "enr:-Je4QEedukMKHefwNNtNam8wvx8_0GMFHLC9nA-TWWPMTZ9FD4C1CrjqFxLNruZPGL4T1ApKPtes_ApKbwwOEc4dNxIDg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIrJ_vOJc2VjcDI1NmsxoQIKnge3StO67CequDTr_QwD_V_qhS7TnGO-tgE472AFroN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "QDMF4HIR2UEXRJVOX4UJPJTPKA.all.holesky.ethdisco.net", - "enr:-KO4QCeMBK9Ur5AHbkdov7TdALRsHzc48RXNtvR861fcmcxPYH59PthmWiT_pQHfVOH3x9-HK2bG8_h2jm46sZyJ6UeGAY2ZCasag2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFs7nOJc2VjcDI1NmsxoQLT22lM9abRweZb93jeMd4jvuRsXnjQFT2oY5a0rb0cvoRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "GSONRYZILMGUJEN3PYQXYD6GYQ.all.holesky.ethdisco.net", - "enrtree-branch:V4A7I7I2SFVUDFFH7AGPFXACBA,67FFSS5IYEYRGUYFEWGIJDIMII,NXU6DU7HEM2L4DN6G2VHHFT72I,3T4CNEKYCJP2PJ3EQVJSFKHH44,KYTZHQX2PSOGY6RQTCVTUKTS7E,GUBLA7OPRPBMYMQ76EXDMB7BHY,HGROZTZI7YPDW5F37QVUPQEBG4,AB27AIPX5Q5J6UAXQ6C2QFTYZA,7UIVPNORDS3RPPTLSNGYAN"); - txtRecords.put( - "V4A7I7I2SFVUDFFH7AGPFXACBA.all.holesky.ethdisco.net", - "enr:-KO4QMIHR0XgkMyX4E9Xh8Fw3rFr-QWyH49lpFmrEhx1Cc_WMxm6atHPm0g-bXmzradBORfb0S9_6dyi-GnQbtlm7GOGAYr8tSZLg2V0aMfGhJsZKtCAgmlkgnY0gmlwhA_MQUKJc2VjcDI1NmsxoQMrgP3au96duBa9PQzAFPhlQ7ikkA96YWAkrItcm5zCxIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "67FFSS5IYEYRGUYFEWGIJDIMII.all.holesky.ethdisco.net", - "enr:-KO4QHY48cNs9Lauv6W3XCDcuBvhJbmL-0MAASBah_V-jG6iODMGVIZ9xv4K3MHSqQQht3Fc39CjU5zoYROe6D8uOq2GAY2UXNmdg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMb0yKiJc2VjcDI1NmsxoQNjc7k9S4kjTIwltOf7EH2ZmK3vZiEBpxxNqondpU3qV4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "NXU6DU7HEM2L4DN6G2VHHFT72I.all.holesky.ethdisco.net", - "enr:-KO4QLbrxgLUdY0NhpBPVfilpwn4d3gPFIEqoq62P1piOkVmGwh5qpbKtor5FfE4FM_eezlzMdeCM7bzau8ciKZU1GiGAY0-AhsZg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEp2iPuJc2VjcDI1NmsxoQOGZRfYj6RgBYWulaw90MVPDt_9F-oM3PzevNe-RS6KyoRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "3T4CNEKYCJP2PJ3EQVJSFKHH44.all.holesky.ethdisco.net", - "enr:-KO4QKNzTVC-wa7whLxFFss7NzEFDdVuAjgEebNGGK9xTWKjHTjqmMNfY4_h90iqlSYM_LoGyH2FFXhbHT05zKyALMSGAYuqLQ8Sg2V0aMfGhJsZKtCAgmlkgnY0gmlwhA360gaJc2VjcDI1NmsxoQPSUi99W8lkrquAhLsmslwXU4-dxxVfied-w_yqYEXpU4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "KYTZHQX2PSOGY6RQTCVTUKTS7E.all.holesky.ethdisco.net", - "enr:-KO4QJn2ahKcTeE2karHSHOiJgE9b2BJWo54NS3nzjjCDnsvZ1JjDl8yulvzn8qQA48zCtHkiVMEVNHtdf_LM_N0Cs6GAYxElMbzg2V0aMfGhJsZKtCAgmlkgnY0gmlwhJ3m3GOJc2VjcDI1NmsxoQJzs6KencrR0_ylBVNGuZaknO_zQUYOrq4rjj_FmTlHN4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "GUBLA7OPRPBMYMQ76EXDMB7BHY.all.holesky.ethdisco.net", - "enr:-KO4QIFA0MiYPPyzvlUqi5j1dL1RGz6MdFhhLN30iXeNc1JfCS1QqTJciby7ZhlQYwFdVMuk_ptgu530WxiQR-UmZpWGAY2ia8cWg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEt3l4OJc2VjcDI1NmsxoQJRcQCiMhxUex-gtsxf2IWJ6nGita_F8BaPZxTwE310WIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "HGROZTZI7YPDW5F37QVUPQEBG4.all.holesky.ethdisco.net", - "enr:-KO4QI3-xQxY2u-misXaxFtpfezZr0Fk0jiL6U7ZAbwV4yCRcWxgNVOxLifoUOm9uMUqH4wlGxIYL7onmzlavqRIjDGGAYs5av4dg2V0aMfGhJsZKtCAgmlkgnY0gmlwhJRxqGeJc2VjcDI1NmsxoQPc4dhahamqQVxv9D89fNMO9DOP_YaPJRQ3EmT2XantU4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "AB27AIPX5Q5J6UAXQ6C2QFTYZA.all.holesky.ethdisco.net", - "enr:-KO4QNZrQF9iyry46odExoUh3kkHjWWHS0iEEjc8n2R4Orq8Rov4Ar3ozsyWt9Z-dmBtpTfmAo3UYspLFcvtHNzwjPmGAYwWbywRg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFtbbyJc2VjcDI1NmsxoQLD-zctKWrecek1fu3o2IKHUY9rkd0BYeQHrzpqElk_1oRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "OSHAABXYJSRWW35VOMYHZUKJXU.all.holesky.ethdisco.net", - "enrtree-branch:LHFO3FXNRHB5B3YSTKUALGMEAI,5FNWKKKVUPIHRKWZY5P33A7E74,NHLNHXPRKOALUKH4MZ7CIVDONU,BAY6SKB2RTQCM7RTSHAFQ6TTBQ,ERETBCE2BAPJRSWIEUQV3QPP7M,MFPFKTJREYMESD7STJJOFYDVV4,MOBGCDYGQA4CNBQZDPN52JC5E4,RUQLNXP5XBWPAOEKRUEBAQXXXU,NJCWGLUAGBNZQYPU752O5L"); - txtRecords.put( - "LHFO3FXNRHB5B3YSTKUALGMEAI.all.holesky.ethdisco.net", - "enr:-KO4QEuoZibAJnKlhcAENpJiju6nVH5J2or_xnVnX_eputCuTHBVtNJmoRKgDBU0idG4kbxFPPKaWRQ3TdYeIbsAAV-GAYrax-PMg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKIOJc2VjcDI1NmsxoQNCYX5kXJqI2deaVOtsTmUQka7dIocapHSAfnNn_CTL2YRzbmFwwIN0Y3CCdmCDdWRwgnZg"); - txtRecords.put( - "5FNWKKKVUPIHRKWZY5P33A7E74.all.holesky.ethdisco.net", - "enr:-KO4QMD5jLnGVTSO75tQPB1JOCFKmtSPRi5pmE7wMwjDRfVKdwpZJvr8qfv1WmfRnJFzH5qV2sfC187nT9mp-xE_-gGGAYupHwgxg2V0aMfGhJsZKtCAgmlkgnY0gmlwhE6KPgeJc2VjcDI1NmsxoQP9YVLocwkml9Z9YNDRpmzVceRY-Ts45qiPOBC1OsxuUIRzbmFwwIN0Y3CCdmmDdWRwgnZp"); - txtRecords.put( - "NHLNHXPRKOALUKH4MZ7CIVDONU.all.holesky.ethdisco.net", - "enr:-Je4QBgb-GKZRRjU2TAE_9KyBHC8ImLYDPG8KShRKx8gbKF3F-gpoHiyeprH42weMNSg6i8GeG-n0SxwWJRe6zyXj88og2V0aMfGhJsZKtCAgmlkgnY0gmlwhCV4sKqJc2VjcDI1NmsxoQNuVgNhx7pgtJotEBq56F9YFom-6szZ97gCkea35_lDkoN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "BAY6SKB2RTQCM7RTSHAFQ6TTBQ.all.holesky.ethdisco.net", - "enr:-KO4QIo--Wic2Dyk2II8x2uTPNOFY3CRZ8wmABP5qOhmZv4LXvAfbdISPp7HDrJwrejs032LHH5rhUY5l8bj9-M-_u2GAY3GDGQWg2V0aMfGhJsZKtCAgmlkgnY0gmlwhI6E2M-Jc2VjcDI1NmsxoQM46NJn0fjSRwgGkB0G1n7QQ9VyBDwiPXPX_xgM25tDOIRzbmFwwIN0Y3CCfEyDdWRwgnxM"); - txtRecords.put( - "ERETBCE2BAPJRSWIEUQV3QPP7M.all.holesky.ethdisco.net", - "enr:-KO4QJY9GUtSCCXanmC9p7LYAfqk7jOUF-zxl7zd2ce5lD_aSNk9KfzBbh-Hii3qMzv-2x4a0Xal7RQMzQv9BqCkUMeGAYw1cGzgg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEVR7SJc2VjcDI1NmsxoQPQjk4qnGis3WKuSQDD20H-9n9xKFTbnLsB1DhAteyYk4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "MFPFKTJREYMESD7STJJOFYDVV4.all.holesky.ethdisco.net", - "enr:-KO4QJfXFSQOpwMtEgz0BSB0gJ0zXQZ6yMzoBfXYCK3ATxulM_mkkQhMWUHsrNyNH9cQYfxRbN_hEGfP3id3IfLiv7KGAY5iEk-hg2V0aMfGhJsZKtCAgmlkgnY0gmlwhGQmekqJc2VjcDI1NmsxoQOoFPqpQmRhcIfVINZ0knq8hxe2tUYnTSLEC-6Xoly7jIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "MOBGCDYGQA4CNBQZDPN52JC5E4.all.holesky.ethdisco.net", - "enr:-KO4QDuar8SHEX1o_kUIKB0IajdUqcRb3ZkZdr5_MIxT1BUkLVk8AelLTv-_ioVGRAwNIJTzT4m8nyzV1VbsjjpeZ0yGAYrXy6Pag2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKEeJc2VjcDI1NmsxoQLy0OM9Ze8JjVHZhLQg7XejbI5iedlSDZNRmtQdTy_X8oRzbmFwwIN0Y3CCdn6DdWRwgnZ-"); - txtRecords.put( - "RUQLNXP5XBWPAOEKRUEBAQXXXU.all.holesky.ethdisco.net", - "enr:-KO4QPpoyIf1DfiiOW8Tt34tBuA68Qd55cyWcqGOCFJtcHKtcqYr373NJPyQVd8ktsuqZU8L_ERSRAfKesEwdBARg3OGAY45u3jEg2V0aMfGhJsZKtCAgmlkgnY0gmlwhJRxo0WJc2VjcDI1NmsxoQKhMTQ31q7EAIHkisiA02KVyFZZLq2Q9w4_jgmiYCsF_oRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "RUJF27NEYDBNQAFMI6K6SKDVRY.all.holesky.ethdisco.net", - "enrtree-branch:OOQAXEUVKR5EJ722HM2UANT3R4,HOXANHAMPVCFA2QR54VEMWCWPE,INA5QYTPU6WIOBT7IC7U3WGDIY,3PMAED3BC2HUS7AMEPGJDKENQY,VJJPHI6PWJ7NFBTW2XJH3JMMNY,5ZWIGTGKMFAFF4LAODABANSBOQ,UYESJQUB2BHP2MSJNUSDRGXQ34,CK3HQLGRQATDVF5CEGHX4DVQRE,AYB76YY6WPJVIBLRB5U7L5"); - txtRecords.put( - "OOQAXEUVKR5EJ722HM2UANT3R4.all.holesky.ethdisco.net", - "enr:-KO4QBU5Jlmco2sEPOXNEctSfBJfxdgANrWXVLS-BVDQYrU3N_oaeceDQIH82zZ6BSi_F-FQpSKhEVYtPDxI1BA4OW-GAY5bg91Qg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIrJH32Jc2VjcDI1NmsxoQMXPocPKx0wRocEEzASwUfzDLKd1f-Jifv2pyIu2M8vaIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "HOXANHAMPVCFA2QR54VEMWCWPE.all.holesky.ethdisco.net", - "enr:-KO4QELWztCXUz9MCxD8zbh65aa6LwP3wubhdXJVdlyrl7QzFJkUXosDXndbQqtAT2qD5nXAFpBnz5MeUSZf_GfjkI-GAY16vTc2g2V0aMfGhJsZKtCAgmlkgnY0gmlwhCZhyOSJc2VjcDI1NmsxoQMGwfOevbfZOGp3bL_2AH1SBdx3zn2_l56uG_4CWDf66YRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "INA5QYTPU6WIOBT7IC7U3WGDIY.all.holesky.ethdisco.net", - "enr:-KO4QDpTzc6voIJiaQO1T8cbJvPej2OvifSuuDkQVrAcC3imJXT9J9zt5DelQaMAeu_uk11kFFgYJHBAE88J2cvxYGqGAY1olkLcg2V0aMfGhJsZKtCAgmlkgnY0gmlwhLnRsD6Jc2VjcDI1NmsxoQKfdroTKrg5QqtuIyfF7LivDR4GjeBnA8xQrAg5ma4pzYRzbmFwwIN0Y3CCy_KDdWRwgsvy"); - txtRecords.put( - "3PMAED3BC2HUS7AMEPGJDKENQY.all.holesky.ethdisco.net", - "enr:-KO4QFMsKiZk64UQsyLVHrxTpTpmdZYzuI-Xgy0gut5NgLulRvbrgQDmdrAT2PQuxK8K9AiplRzJ_i8CtseBD_d2bwGGAYx9HQirg2V0aMfGhJsZKtCAgmlkgnY0gmlwhLBn3miJc2VjcDI1NmsxoQJSdj6d-7J6YsK6vZwRl_zzMSCKauLadYxL8zfjjzOQZYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "VJJPHI6PWJ7NFBTW2XJH3JMMNY.all.holesky.ethdisco.net", - "enr:-KO4QHCu9ZPe68fKUXeUzje0Jwv0D8UCrAQ_K5W9KHrIpTdUYWIGwbzyl8heZ1cxijlyUhN1WB0HZpEBThUmrdm-uLiGAYra3Fk3g2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKJOJc2VjcDI1NmsxoQP0AAgBs1kDG4XBe55HY0J4F7GjHiwQVUY-xFKYXOB594RzbmFwwIN0Y3CCdnSDdWRwgnZ0"); - txtRecords.put( - "5ZWIGTGKMFAFF4LAODABANSBOQ.all.holesky.ethdisco.net", - "enr:-KO4QFP5g9U1YtBLeJi56xoBll8E4eUJeAAYMoEg1l0xPXRybh0OYNZfteBjkHMwbucccNdGB0amzNR4XtxmAfjoFWKGAYrbEjd8g2V0aMfGhJsZKtCAgmlkgnY0gmlwhF_YY_eJc2VjcDI1NmsxoQJb-JLk8MFQQPMz3QI2ya4FtZdHqZ1Nm0xD4MtOYc9ifYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "UYESJQUB2BHP2MSJNUSDRGXQ34.all.holesky.ethdisco.net", - "enr:-KO4QB9gKzGbKvqoPJZymq3AiwZvWSHOup-36D8ec3CwNZDFRoEmp64UmWaP0St3OXDIV4Q2Rm0MnelRnAjvn3HRfyyGAY5EPB9Jg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIrJ3_qJc2VjcDI1NmsxoQKHt6N7ehDnwT8_lFh-C4KgqUplYwDvYy6rOP6DFRO8lYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "CK3HQLGRQATDVF5CEGHX4DVQRE.all.holesky.ethdisco.net", - "enr:-KO4QJy9FHDJZfMqgGGIVGn-j0rHI6JDjTWEo7sN1mBkzO68ZId8jkRQfgZ5dJkv4GGm2rsvrWk6OJYF0td7n0Ve9GqGAYu0QPj0g2V0aMfGhJsZKtCAgmlkgnY0gmlwhHTK4wuJc2VjcDI1NmsxoQIYyd-9-wdwfPPu6uRcnTKkQaCekdhR7488-2pxl36AZ4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "TXAFU343ACQLSVIMT5LYG3W2AE.all.holesky.ethdisco.net", - "enrtree-branch:3EMD3PTQYRFVNFBQ73BYFL4AEQ,PMLO73ISEK4Z6IRPOSWIUUKF64,5NHWEAJHKSAO5DIIYAZ7MQJVSE,LNO6EN3Y2LJA5YOJMEMODAJFWM,23B5UFB3JWETLIKE5Q6B2B5MWA,HQ5S2EHH762PSKVTHWNQ6346NI,O25DXMPE4UEZ6SFMQRBGL2E2I4,2WHIXLN5QX3JEK3SSH7MXKTJMU,WQDLDQ7U74Y2HM5LHVCOCY"); - txtRecords.put( - "3EMD3PTQYRFVNFBQ73BYFL4AEQ.all.holesky.ethdisco.net", - "enr:-Je4QFiHIu4B3YbbG75mlr58JRaqpGUQij1n1pN3zaHoWbGrShq8aYqipbkPLaINPTX02YBfchyACgLHgH9cMarIUapQg2V0aMfGhJsZKtCAgmlkgnY0gmlwhDNRao6Jc2VjcDI1NmsxoQJHJlekJODLxBDPwpVLBtTMizLq5o9JtXkD8MQpqAIUYoN0Y3CC2fmDdWRwgtn5"); - txtRecords.put( - "PMLO73ISEK4Z6IRPOSWIUUKF64.all.holesky.ethdisco.net", - "enr:-KO4QBxZ1JFNQEopzO-wMumFIw4fGHkuPZkuCLvgcz0X8L8yKWUkQ-UiS7-KHBCjGOby7yjR6m7m9dqoQvlHejBncSWGAYv2B0KZg2V0aMfGhJsZKtCAgmlkgnY0gmlwhA_ML-SJc2VjcDI1NmsxoQMEi6SKsKUVTnxDWKc1Go6ZG8o5nEapNJBaWAd_YC26R4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "5NHWEAJHKSAO5DIIYAZ7MQJVSE.all.holesky.ethdisco.net", - "enr:-KO4QMiOfa0JolrO470F-mRKslpq85BBGRz_JjoAOOnLuUdxBObP7579igrB-YGSdaUtb3Ih9QXBHRAl0PWYmE5NW32GAYtIvzSsg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMF6nk2Jc2VjcDI1NmsxoQI57ZbbGPYfl0DtrsmjeKDosRG14eV2qLI8AE6FsP2oy4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "LNO6EN3Y2LJA5YOJMEMODAJFWM.all.holesky.ethdisco.net", - "enr:-KO4QMIX_--Ar7-XnFcUVlJpBBUfZdfNOftELgvHhCGvbLGTMT_Yqs2M15BCG0qoHsYkVBP5b4wd2wqNyNIfFaTtQ7qGAYrXxOTEg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKEeJc2VjcDI1NmsxoQKG05NXftvaats582r82_zpOi2C54WjSeYwxKfSNeSJaoRzbmFwwIN0Y3CCdmqDdWRwgnZq"); - txtRecords.put( - "23B5UFB3JWETLIKE5Q6B2B5MWA.all.holesky.ethdisco.net", - "enr:-KO4QGjsmd6RDlGYHJu2JnI-Lf4TZ8s1yJtLDvvtp_Kw6xIwNn7_Ti_FmEHfWbaSyy0Icl4jlErvH-LrEmi9PCzB27uGAY0MEI03g2V0aMfGhJsZKtCAgmlkgnY0gmlwhNXvxn6Jc2VjcDI1NmsxoQOzxdn7S7O58IiaWGk4n3MsiB816GGv_bAd_PM9FkWeZIRzbmFwwIN0Y3CCVTyDdWRwglU8"); - txtRecords.put( - "HQ5S2EHH762PSKVTHWNQ6346NI.all.holesky.ethdisco.net", - "enr:-KO4QK883RN_CIppuOvhzi0O3Qg-XzHUZsdKIEqLdvZMLN5BCV5QTqqxRzJjtXa7NZjBi22UQJUUlsrQ_3g3Uavv92aGAY1BRdWag2V0aMfGhJsZKtCAgmlkgnY0gmlwhFJkOnSJc2VjcDI1NmsxoQNAyMtRruAQcNn10ZReZiiZtPnk35o_oJEhabuBKi1gLoRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "O25DXMPE4UEZ6SFMQRBGL2E2I4.all.holesky.ethdisco.net", - "enr:-KO4QLC4qJt9OIKLkxROeQMNcj4ZuH_QZsVcS1v-NhzBGAf8B-5xF3GYqtMp40n6nwDg8o1yUs7xEx0kgf6wBJF-AuqGAYrbCvwJg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKGuJc2VjcDI1NmsxoQPogWsyNFpt9VYFYrsdUS4dxK2BMeL4FY41ZzgBjeEGRoRzbmFwwIN0Y3CCdmCDdWRwgnZg"); - txtRecords.put( - "2WHIXLN5QX3JEK3SSH7MXKTJMU.all.holesky.ethdisco.net", - "enr:-KO4QF2wWLadNPZ_qwOIZkLxPEJjj3N23892JKdtSOeeC-X-V0bZsjQ3QqpidJviGT0WrBakFANcbSa9IqFSOslJLV6GAYrayMEMg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKIGJc2VjcDI1NmsxoQKQ_STV3aCaM5we4KxMPMpdG63xPZieKw8A36OR2tuouIRzbmFwwIN0Y3CCdmqDdWRwgnZq"); - txtRecords.put( - "R6EQA5KQEQM77JJXB4BHHTDF6Y.all.holesky.ethdisco.net", - "enrtree-branch:5AOSND63QKPVT6EWNMALKAFC4I,WAU46WR54TX2VBK4QM7NFTA73Y,BBNHIHYBPXVQTDZQ4JXK4QYDMM,OA4ZFFGZNFUC3LIYCUDJOPVR6U,KMY5HFBGCUDWN2ZXE6QNUQHGYY,OGKXEQEVGNJ574WCU7KFNTXNQI,CDXRYZFKH6OUWTA2IJ2MYIMYPM,IPU725BUGL4HIOTSOIH3KZG5ZA,G4MENE2MCGXFPYEBF7SQEU"); - txtRecords.put( - "5AOSND63QKPVT6EWNMALKAFC4I.all.holesky.ethdisco.net", - "enr:-KO4QKn6DlOQ0ybfLAfyPlyPssuWtP0Zu5FEUEgr3015XppiCP4yr9SeMgnpN90AVHJA5C61F3677GiO0N-JIXGfN2uGAYra2k7ag2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKJOJc2VjcDI1NmsxoQKgKJovbRS4hL3ugMVrevOCUGDS-ixgByq_tbh4T9oihIRzbmFwwIN0Y3CCdmCDdWRwgnZg"); - txtRecords.put( - "WAU46WR54TX2VBK4QM7NFTA73Y.all.holesky.ethdisco.net", - "enr:-KO4QE70KmVYr_jd9JOMBFTIJomf3oliyhugCm4kFnzYD7dcJk3y7dVYyakJdlWHBuk1t4hDjW05BzeVf2f-V0LQ-r6GAY0-Aab1g2V0aMfGhJsZKtCAgmlkgnY0gmlwhCUbP0KJc2VjcDI1NmsxoQMQLfyyzC-YY6A53fEu5a8tP-JWCUM3vkX-c7nRyu28d4RzbmFwwIN0Y3CCequDdWRwgnqr"); - txtRecords.put( - "BBNHIHYBPXVQTDZQ4JXK4QYDMM.all.holesky.ethdisco.net", - "enr:-KO4QCMGWCueZIoaBJyNAqOo_1wJ-QmbQ-qlTQx_rfz0gzKjF8VqFc99Q3ZMcfpK2qdLtWZHoRoiCkstH9_rWAU49DaGAYrbEkgdg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEVzHeJc2VjcDI1NmsxoQIy6rguhP2KemBUH2HJyW-GPo8prt6Ay9gjX3GugxLuGIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "OA4ZFFGZNFUC3LIYCUDJOPVR6U.all.holesky.ethdisco.net", - "enr:-KO4QFuTcCcZkhGlEZ9YjHVURcwzRDKDu_o8RBID9eF5lmG9fb9ZHBHnz0E-tXO4TcPfwxEtefRAaAVPj9Ycdi3OeXuGAYraylBgg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKFKJc2VjcDI1NmsxoQJRTkcLLW6c3J6kd1b1siWyumgXACvQK7ViQjsgMFx0c4RzbmFwwIN0Y3CCdn6DdWRwgnZ-"); - txtRecords.put( - "KMY5HFBGCUDWN2ZXE6QNUQHGYY.all.holesky.ethdisco.net", - "enr:-KO4QLBXgetH_VHZgzalnkfF-iibF4km3OCLzY1TK27U-yG0ZA7Z4C8DL-da5x65khpgvOBa8c2CXffZBdTtD2BZQCqGAYrg47Ahg2V0aMfGhJsZKtCAgmlkgnY0gmlwhJT7RHyJc2VjcDI1NmsxoQKeRdcysv01DZ_Mzk9t1jcWW-YTjP1fPIrHVpw9hc_Q_oRzbmFwwIN0Y3CCdn-DdWRwgnZ_"); - txtRecords.put( - "OGKXEQEVGNJ574WCU7KFNTXNQI.all.holesky.ethdisco.net", - "enr:-KO4QH_WNLhz4qmL-EOMCQFfhE3QOOvyG5uyX0RR77gYrsdcJnUxS5OBQWyMqhRQLO2P9htXeBxJ4kR9ez13qQRiBp6GAY3m5RB8g2V0aMfGhJsZKtCAgmlkgnY0gmlwhIj0RveJc2VjcDI1NmsxoQJFRRKo2aP4qXB8sZnA6_rJqKgo8FZGT2i3ipEzwXCayYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "CDXRYZFKH6OUWTA2IJ2MYIMYPM.all.holesky.ethdisco.net", - "enr:-KO4QExy2s5Uh2eTEJtsODBEOcPZZN-CYY0WEr_8nA_uK43QV3iqmFzfXJ-29zootm5F-E-DrjzTObp2tw_klTXPRu-GAY2ZtbV8g2V0aMfGhJsZKtCAgmlkgnY0gmlwhBJ2VQmJc2VjcDI1NmsxoQJuk1X-4JUVQT-mguV7dSGSUC-RoxdnTyOmD-VrfRFQyYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "IPU725BUGL4HIOTSOIH3KZG5ZA.all.holesky.ethdisco.net", - "enr:-KO4QLf3R97a5p3pRgY1AeN1DisWZUTnHbIMt2aPh0zvq8QIZguS9EiujNa71YXqJgxw7ztz3YmwJMWuPuhfWqSkxqWGAYrayxVYg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKIOJc2VjcDI1NmsxoQIHu5vOuFNLO8814yybwdh03yUQzfPTTyeK_X44Gz5jx4RzbmFwwIN0Y3CCdn6DdWRwgnZ-"); - txtRecords.put( - "PL27G47LAASBPPAMXUDIJ3OCRQ.all.holesky.ethdisco.net", - "enrtree-branch:3JSDZJCCWKIEBGPH5EI5VZF4RQ,XTJ3PFTPBB3ATDDWAA6W7RRKMY,FTTHOFPRPIHMJNZA3VZUAW5TIM,DAVKEW6RQ5YTYHKCJ2A2J5GVCI,TGKURK5IPVCPXS4QPS2KZJCP3Q,RWRV55FT3DKDVGZK7AEU2DR77Y,CTDMY3ALJA7FRKVS4MCTAJRRH4"); - txtRecords.put( - "3JSDZJCCWKIEBGPH5EI5VZF4RQ.all.holesky.ethdisco.net", - "enrtree-branch:4PFOQY7RKTNLRPXQXKKXD3K6LY,3AVV65MYCJ7AGFUCKXT2UX2DSQ,5JAERI2BPJN45X7IK6SWZOZ2FI,YUZZDZD57PTNEIB5QL3FEXK2ZA,TMY2W2YBNCXUUNA3Y7QXWVQLRE,MJB42632KKOPGJ2KNY3GPKP66I,QXRV67JYJNOYJDPC5CTKUVG76M,QJM2RVJT7USPDSI5VVFGGDW6JM,I33RZWBACJHNDEXG2RRXMX"); - txtRecords.put( - "4PFOQY7RKTNLRPXQXKKXD3K6LY.all.holesky.ethdisco.net", - "enr:-KO4QJ8BuQCt7gBXU_26FNx8waHuCzCkG54mErQoBAJrYaw_bdUJT0yQA0vG8gr-dUoS77OX0D4tD-R6e30832ncLMaGAYrbEjQ3g2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEV4tiJc2VjcDI1NmsxoQJHEsWJ3CoSi-1JgkeSogpyRqYvBaJI77-soOcbmB_nyYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "3AVV65MYCJ7AGFUCKXT2UX2DSQ.all.holesky.ethdisco.net", - "enr:-KO4QEaesvQ1kySNS_mnQfiBr4DZQmkhHmqKEGRQvgyd5K3lNDm3vjs9cMMVTY9FgENpP1o0UGBsapNNfvXIN2gaudGGAY3ulUKIg2V0aMfGhJsZKtCAgmlkgnY0gmlwhDIjTkmJc2VjcDI1NmsxoQNVbUX0SvjLX7d7psFtTTNkKT7x_zHl3f4bSVQrkbK1YoRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "5JAERI2BPJN45X7IK6SWZOZ2FI.all.holesky.ethdisco.net", - "enr:-KO4QEsCIXXCDLUsTbUOT3ILg3rpga0gZXaG9_YT9_kFk97iRwu_Pr4RlhDvdkg4Y4bl9SBVzP9bFtL7H9AU7QxE7OeGAY1lbo59g2V0aMfGhJsZKtCAgmlkgnY0gmlwhA3Xl1-Jc2VjcDI1NmsxoQPsZi79eOcjTI3Xa2GCBiXRCgtm7wFo19cVlmhQSZ-i64RzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "YUZZDZD57PTNEIB5QL3FEXK2ZA.all.holesky.ethdisco.net", - "enr:-KO4QKuFVKbKbtK1djMy6q3TLuAWYDAjoC20cYPdhcOk7PhUdA67TXr9vTGfZZ9AO7ivmQZyRW4w9TFk-29_xP72bkKGAY2EAMvIg2V0aMfGhJsZKtCAgmlkgnY0gmlwhF_ZKGKJc2VjcDI1NmsxoQN4FBojS_W4gviNwaTDsIXiBEGXaQMtUzxQHwKWDmMR74RzbmFwwIN0Y3CCequDdWRwgnqr"); - txtRecords.put( - "TMY2W2YBNCXUUNA3Y7QXWVQLRE.all.holesky.ethdisco.net", - "enr:-Ke4QEsOrFCJqnbVXiuSh3DKjvS3RlUoOrrLRlKp2mAqmCuJQBVLFREqABNWRvZfLOxDPEtvcRzDtDW2juSa1vd2xj6GAY025-JNg2V0aMvKhP1PAWuEZcNqwIJpZIJ2NIJpcIRU961viXNlY3AyNTZrMaECoqR94LV1BQMpEDbmfJdd8adEPWHV7qP09nePPVm9nLqEc25hcMCDdGNwgnZfg3VkcIJ2Xw"); - txtRecords.put( - "MJB42632KKOPGJ2KNY3GPKP66I.all.holesky.ethdisco.net", - "enr:-KO4QEBK_p981nqXnme4a2sYvHW2FOz_OwLwstllVRvE8w-dVnDBrHxVZg7XSQwseErDQpfUgYMnBPMzpsx_bDUui5eGAY09OSoRg2V0aMfGhJsZKtCAgmlkgnY0gmlwhLzWgw-Jc2VjcDI1NmsxoQM2fE-8xAAaBUMu7dwviv_y8osp0rYMaKtaGBnbUba_J4RzbmFwwIN0Y3CCdl2DdWRwgnZd"); - txtRecords.put( - "QXRV67JYJNOYJDPC5CTKUVG76M.all.holesky.ethdisco.net", - "enr:-KO4QN1MRyJdifSbU3Mg_GUI0_ApfQQ_BcPgDV0FXjVvk4iEWX6EgMjXkI62H1S0DFnyWeHnvr-caabqiKtlvbIBICyGAY5Cyclbg2V0aMfGhJsZKtCAgmlkgnY0gmlwhK9jhAaJc2VjcDI1NmsxoQL8tsMGtv3EuJfUdI8AzGBxYynVj2HlNoz4wi3COC4TTYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "QJM2RVJT7USPDSI5VVFGGDW6JM.all.holesky.ethdisco.net", - "enr:-Ji4QHWntDnLOi4S9GKmF4HY7WrnJuJGaIBrnM3Y75G-8EUpZExnUkaZJtlmzOeJs6WAcYsissKSybUG5rSEiad0s86ByoNldGjHxoSbGSrQgIJpZIJ2NIJpcISLY0UEiXNlY3AyNTZrMaED5hLZweFA_nvWEc-Q7Vxqf2lIYxScF-F4ORxs-rVyHZKDdGNwgtn5g3VkcILZ-Q"); - txtRecords.put( - "XTJ3PFTPBB3ATDDWAA6W7RRKMY.all.holesky.ethdisco.net", - "enrtree-branch:5AHKPN5NN5IHEH365X6OYZDHDE,AUQGHXRAP7J3AGI6TUOQVYTYLY,IDXHFSATDDENJYOYJC2TUSUYOY,GZVG4EONSAY6M7SMLJXDCFUF5Q,LHFTDS37XFOEXQKTWJGKWRLFWM,HTMBFBAIZBACDG3EZZ4JVANCAY,ZM7CMNKKBVIXY7WGMB7GHYUXMA,VNVQZHCW3CS7ELWR3ANLO7RFBA,PA4IN34FHBJOUK2MS7YRA3"); - txtRecords.put( - "5AHKPN5NN5IHEH365X6OYZDHDE.all.holesky.ethdisco.net", - "enr:-KO4QM-W42wfRYcTwaegJy3SfjlCkZsQU8LhGcluTdxtFjqXOwsEE5kUewkPMe7qIQpwb2MAo5xTeRpnijbdYTaG37-GAYtxMJdhg2V0aMfGhJsZKtCAgmlkgnY0gmlwhKh3IxmJc2VjcDI1NmsxoQNDrMpeyQI2aO_yoNP41RAf_BHtckptN1l2QIWxgDKOC4RzbmFwwIN0Y3CCdl-DdWRwgnZi"); - txtRecords.put( - "AUQGHXRAP7J3AGI6TUOQVYTYLY.all.holesky.ethdisco.net", - "enr:-KO4QGuiAZHlxH7T5XPcPpRTovca50B0pzDLkgJ-zaxkjT_PO6zsAWQezB-b53yAnqx01Jdj5tQWsvWoaNVfiFw3iw6GAY1nSgXzg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEp2jJuJc2VjcDI1NmsxoQMTiH7XteYOVAHiXAZoacUjo5JYLhQINeFMbPbEY9sA84RzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "IDXHFSATDDENJYOYJC2TUSUYOY.all.holesky.ethdisco.net", - "enr:-KO4QNWpVQT5pw-oUuoa7JDOVs1KheGaPRAR_fIarAflnc1yHawwEpptaB5JWkRXgh-wJtk2KlHE5zGICXefHD23Z4qGAY2-EYHHg2V0aMfGhJsZKtCAgmlkgnY0gmlwhK35GlyJc2VjcDI1NmsxoQPV0hDwmz1HaZ4pLm1hzee38NMugxA_7j3v6hbxP_LRCIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "GZVG4EONSAY6M7SMLJXDCFUF5Q.all.holesky.ethdisco.net", - "enr:-KO4QErAUNKF003oZAYn8UUocPIuq2MTpxHV3gHfrNkThYPbZ366m-mlvXezUX5JDA5iJE-LTWWEPh8JqjEDah5kVimGAYxA2nMcg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFtWv2Jc2VjcDI1NmsxoQMCgMWZQXkPyMKdwnzdBxQe6rcs25guZtULvfzDjhE71oRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "LHFTDS37XFOEXQKTWJGKWRLFWM.all.holesky.ethdisco.net", - "enr:-KO4QB0RN-qZSymbzZA-AlKT8213I60xJjVVwN-AWwjW9W9aLk-2aq8jW_jbgKnJY3c_wU26oBzYFBpOVcYKIV_nd16GAYwlWhtPg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMPJ9bKJc2VjcDI1NmsxoQKlc1OTSAanfHEsPtrKjZvkgQUuHFZon2GtnCRHlDRV7oRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "HTMBFBAIZBACDG3EZZ4JVANCAY.all.holesky.ethdisco.net", - "enr:-KO4QHHstlNCTI8xMnZ5mcIRvd8lfo9YoVeyv4pqvhtkxGI5PhYqDDqu1W5XqHWCMcTt8fpVgu9KojZHcyi7fhQhPdyGAY2_zUcvg2V0aMfGhJsZKtCAgmlkgnY0gmlwhL5czy-Jc2VjcDI1NmsxoQJpsTl5abUECWCfSno785tWBNPiVCJ7GG4eQTf2Cf1qBIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "ZM7CMNKKBVIXY7WGMB7GHYUXMA.all.holesky.ethdisco.net", - "enr:-KO4QNhl-I-sZux_gBOVLLbMdw8kMK9fSEBBNYukE2r95hDLJ9rOuhZgJO2dvNU_vCcuiqJNJHj8N3olsc7srxryuuCGAY0cB7Qsg2V0aMfGhP1PAWuAgmlkgnY0gmlwhJ7caOWJc2VjcDI1NmsxoQIyp37cpS7gvrKk0f3VxW4D9Jx7mOvSAQ6vOaQppsYp2oRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "VNVQZHCW3CS7ELWR3ANLO7RFBA.all.holesky.ethdisco.net", - "enr:-Ji4QBY7W0c9-rsM9r3yeBngIOe0LFJx8wew3ZatidckbRSzAbXbvc5ahmadpUcrPG2oDm9ziwxw7maQtzEs4J2AnWeBr4NldGjHxoSbGSrQgIJpZIJ2NIJpcISU--s8iXNlY3AyNTZrMaECwwCgmDrePUxVSsOZqgS0USzkXwzkqF0-QK7a59EsnrCDdGNwgnX7g3VkcIJ1-w"); - txtRecords.put( - "FTTHOFPRPIHMJNZA3VZUAW5TIM.all.holesky.ethdisco.net", - "enrtree-branch:V52J3WKNVGG56JFRKYDLSZREJY,ZCAW4EMBOL5EH4MSMW7FY6JMEE,2SA643JTHJ75ZVPFHAKLXI2WXQ,5RYAVZN3SSZZQ2KISUO7XDBPE4,366Y3UIIKK2G5CUS7CHNPZMPEM,7J7D5R4SH6LVDCFZ5ELOP46IP4,7QEPEVJFOCL5A63GZLZ4IZNMOI,OOWRWZXNDJVJUCDDOJFOY4YB2Y,H6XMM4W6QZEQDAPVXGHJSU"); - txtRecords.put( - "V52J3WKNVGG56JFRKYDLSZREJY.all.holesky.ethdisco.net", - "enr:-KO4QMC1Y7Fl44QyBr8KDFTyd1IQ_h0w67Igms87RDJeZJuXbc74aIVNkOeK0t3_zEJAT2spttSVu8tKDeV-cdiPE42GAY3KEqdCg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFsGDWJc2VjcDI1NmsxoQN24zvy5pQRtLA5_iKLgnTwXjI3T1KldFTOyp7Dk6dCRoRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "ZCAW4EMBOL5EH4MSMW7FY6JMEE.all.holesky.ethdisco.net", - "enr:-KO4QHc4CzkY6KkiTGR6hTovOeJ8VJuUtjkq2-UMXwUijiKvAZOFVS_Nn_GHMVZ8Ppsu31rSLYAEFRu0tMJ-LIDdSjOGAY2sYroCg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIrJyaeJc2VjcDI1NmsxoQN7HHah7WnGluJb0mqKelBJ8rpvKlBb9t7848BwJ4qau4RzbmFwwIN0Y3CCIB-DdWRwgiAf"); - txtRecords.put( - "2SA643JTHJ75ZVPFHAKLXI2WXQ.all.holesky.ethdisco.net", - "enr:-KO4QEPFY3aDBt5F3VQU5qcGZED8Xr-J9v1Fi4CcGpx7q_fydrJM8h0RqAXSdcJbr03b7ysPaOR4mNUTRou4kjPsu7yGAY4QKOX4g2V0aMfGhJsZKtCAgmlkgnY0gmlwhCO4xmuJc2VjcDI1NmsxoQKaOskToHERfimE_ei_VPRsh3fimBlYooVb80PVt0k-aIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "5RYAVZN3SSZZQ2KISUO7XDBPE4.all.holesky.ethdisco.net", - "enr:-KO4QGMFa1U36mwISUTSlJN8q2EKJSw4WHa6PAMrHkhL9-naVdAWmGuwfpel1E5_NBSnPWRsG8FzmmUH61iq8fodXcKGAYrbEkT3g2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFtb_iJc2VjcDI1NmsxoQJ62Txw9OKK7DTE_CZRC6tX_223dks-FcinnSL1uXUbTYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "366Y3UIIKK2G5CUS7CHNPZMPEM.all.holesky.ethdisco.net", - "enr:-KO4QK1ecw-CGrDDZ4YwFrhgqctD0tWMHKJhUVxsS4um3aUFe3yBHRtVL9uYKk16DurN1IdSKTOB1zNCvjBybjZ_KAqGAYtJ5U8wg2V0aMfGhJsZKtCAgmlkgnY0gmlwhA_MtDmJc2VjcDI1NmsxoQNXD7fj3sscyOKBiHYy14igj1vJYWdKYZH7n3T8qRpIcYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "7J7D5R4SH6LVDCFZ5ELOP46IP4.all.holesky.ethdisco.net", - "enr:-KO4QArnrdy9hfW8KHAwokQf-x0LbdoDQ4WoK0H2dXqhP4D-TflLF_Ywf2lh4ybXao8jjNSxC53MzmFPUI9A8k06XDqGAY2KrTRtg2V0aMfGhJsZKtCAgmlkgnY0gmlwhLWkyzGJc2VjcDI1NmsxoQKSAHhJOTtkBlasBECB1mrukuL6jqOEtxWkpH-PfDp8h4RzbmFwwIN0Y3CCequDdWRwgnqr"); - txtRecords.put( - "7QEPEVJFOCL5A63GZLZ4IZNMOI.all.holesky.ethdisco.net", - "enr:-KO4QNGh--C3ckyCMlUP4u86lt292CLHOQ3YIwk5Jqzz5x3OID_7BfRn5qN-ZBoBY80XOhFyHuo4eKDCIowQgYMvVY6GAY5S1aoSg2V0aMfGhJsZKtCAgmlkgnY0gmlwhFhjBqqJc2VjcDI1NmsxoQKZpCZSQgSun9pLzGrdfLboslJbD-JxtTsR4JF860eZYYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "OOWRWZXNDJVJUCDDOJFOY4YB2Y.all.holesky.ethdisco.net", - "enr:-KO4QBKCkXrhG-hN-IY-O03b5A4JHaweb5xuuSZP4OdJ4uSSZ7E1-yioWl_0WSF8ShOfNU_7wgSCIwBPg0d4RD4w4sOGAY4oBl8og2V0aMfGhJsZKtCAgmlkgnY0gmlwhFJB7YiJc2VjcDI1NmsxoQL-RfOTNeu0SmlCfWAVQaQBUXF0WDEUCyGXv1LWC2_xqoRzbmFwwIN0Y3CCequDdWRwgnqr"); - txtRecords.put( - "DAVKEW6RQ5YTYHKCJ2A2J5GVCI.all.holesky.ethdisco.net", - "enrtree-branch:6WAXAZL7FYKEJBZOPWIHCI4F6E,OL6KYVYI7SGS7U34FW3RFBA76A,FKSM7XWAT4KW3TDLRHWSXZPMKQ,5HSHHSS4QV3FKCNDETO7CEXMZM,CZ7ESLY5ZH4ULXKQ54XIFDCOUI,ZRVWX74UFHUWOPCC7LOPLYGIZA,FFM67SMD4N3SK4ZQTY2HOMU4F4,IPSUCB6CICZIW6SERKZL7FFTIM,7XE2DGFKCGOST6BJ46JPNK"); - txtRecords.put( - "6WAXAZL7FYKEJBZOPWIHCI4F6E.all.holesky.ethdisco.net", - "enr:-KO4QCagHy1Q7UED3AJfgVHxFKOn_DRS8UTUg0okE7fW8VTcDCW7wlzWufdWCtdUwHpnaf3EZpRn2YRuo9u4LS-Oh3qGAYrayS-wg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKI2Jc2VjcDI1NmsxoQJYC74L06jkGeWkmh1aSjeBYVvzDCWEvtbd111Vu8WiG4RzbmFwwIN0Y3CCdmqDdWRwgnZq"); - txtRecords.put( - "OL6KYVYI7SGS7U34FW3RFBA76A.all.holesky.ethdisco.net", - "enr:-KO4QIlf7XLihA1hw9d44SB0ENJ40RT1RmF2KrT9a2kRZfZoWPdO1jRi3GNnNSCLDxLo-aFc6uaGi3zxhCD8lg1aZgSGAYuoht2-g2V0aMfGhJsZKtCAgmlkgnY0gmlwhDNR0MWJc2VjcDI1NmsxoQKFvOHK4c-F1tHwdgsUa2yru5RrWyiVP9kRLnrw1kXcd4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "FKSM7XWAT4KW3TDLRHWSXZPMKQ.all.holesky.ethdisco.net", - "enr:-Ke4QNDn6wmnaZU1IYGb9lX8zb2QFUpa_yen8vFTnuXpW3_iah53AKPZX1D05HE23bw-UtR6zXOpCquB9XDdxvGhZrGGAY4oYEAkg2V0aMvKhMYaYJiEZRbqwIJpZIJ2NIJpcIQlPPlxiXNlY3AyNTZrMaEDiVRaEbnUMSPCtpxluUoBgOy3Loaa6SaVW9QK8BGTFJiEc25hcMCDdGNwgnZ9g3VkcIJ2fQ"); - txtRecords.put( - "5HSHHSS4QV3FKCNDETO7CEXMZM.all.holesky.ethdisco.net", - "enr:-KO4QAOJNWWAbN0bM51WFaFZimloFw1eBc7Pi3x7uMejmIToDZxBoAVtvdwTg3p6T0Mwmvuot6uT7PG2OjGxkk3P-iGGAY5cIO3Hg2V0aMfGhJsZKtCAgmlkgnY0gmlwhLAJA5uJc2VjcDI1NmsxoQPdRE5vPW9Wj-HRwzubnKAA-1n0p6CAOGLP-E-GzE9EnIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "CZ7ESLY5ZH4ULXKQ54XIFDCOUI.all.holesky.ethdisco.net", - "enr:-KO4QOQjO7c9J74CpllRngkrEXHLw-W8VqtJXxkLWPRIXC1CFL4TCjEM4ZfRW6MFlqXERaY3h9mztZXLAo1lf1DAS1qGAY2D0c0_g2V0aMfGhJsZKtCAgmlkgnY0gmlwhC06cBWJc2VjcDI1NmsxoQN8oyz6RCujPq6Hj9N8dBn3EeJ4A69HYeYnyLRiXLZ1UIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "ZRVWX74UFHUWOPCC7LOPLYGIZA.all.holesky.ethdisco.net", - "enr:-KO4QBfm8BEr7OIRv6UT1t5mhE2szefodQuIIBKMFnyvbky_K-wff-l6pqLwfLyUblqFNxqM6Xtcnari4ItzO9236DuGAY08Swa9g2V0aMfGhJsZKtCAgmlkgnY0gmlwhC7r5ZiJc2VjcDI1NmsxoQIH5RAmLOvYi50mYcMnglYJlz-EC0E2YsetPVUzvbs5BYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "FFM67SMD4N3SK4ZQTY2HOMU4F4.all.holesky.ethdisco.net", - "enr:-KO4QG4O1MBSzBHtShtqoXXeAycBqcDpuBK2BBwIuK_Qppz5OpJC6Ga3-qDEQJQOtbEYm0yXMhenb50Get_H6Bu9SbGGAY16lghqg2V0aMfGhJsZKtCAgmlkgnY0gmlwhA_rV76Jc2VjcDI1NmsxoQJ1vrJ4fwXiDAhSqIJwbELp2ktd9A0zRLws1li91-aBmoRzbmFwwIN0Y3CCdmCDdWRwgnZg"); - txtRecords.put( - "IPSUCB6CICZIW6SERKZL7FFTIM.all.holesky.ethdisco.net", - "enr:-KO4QD47Vx9bnFcvE6JNAhrLz6GGgPk9GaJtP95ogjSOFHxlcZjnrT5TQzW9Jyx99XO9rzhK6WSvgdFoA9_54QPQcUaGAYxBy9opg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIe1TLKJc2VjcDI1NmsxoQKMO6bMfiH5DnJNFZgSqUEi6uHNnH2_bMh7hwVXRWTMl4RzbmFwwIN0Y3CCdqmDdWRwgnap"); - txtRecords.put( - "TGKURK5IPVCPXS4QPS2KZJCP3Q.all.holesky.ethdisco.net", - "enrtree-branch:E4GGBOZ5L2UFPAKJIPLNBA2MKA,OF3GRGFZUAJTH3T2R2EXSYAZOU,D4GT5QUSIVDVPURCUOLE4WITZI,O6EQKGUJOO2BQGMADYZHOSZXLA,WRRS22MCKTZ4YS3ZLPUJEUAHUI,3A6T36TYZJBFLOHMJOJGL5SPXA,XPTWTOASNO4WEBXH74WCQ5EYTQ,2XMYGJOCRIHHMHAFVRH3OES5QY,FYJOHHN6LBARRNNLI6SK42"); - txtRecords.put( - "E4GGBOZ5L2UFPAKJIPLNBA2MKA.all.holesky.ethdisco.net", - "enr:-Ke4QD_Gz-YGWvzpAe-a1l2KHz2pxG_nPUYTcHQDN1Wuvkk7TrEDq-HKIXtA_mbPukz9qLLW5sfkJH5mYda9eDssvkSGAY1Mq2nIg2V0aMvKhMYaYJiEZRbqwIJpZIJ2NIJpcIQf3GvbiXNlY3AyNTZrMaECyosSeovqxsDXTua8c47z7EK--WogcD_FCwDX6tHvVQSEc25hcMCDdGNwgnZfg3VkcIJ2Xw"); - txtRecords.put( - "OF3GRGFZUAJTH3T2R2EXSYAZOU.all.holesky.ethdisco.net", - "enr:-KO4QDPbolwwOMTwV02zqyVgJFxko-HI8UWOT_K9sDcWgHG9fRDWkbSbTw_l3bcIA2-q3054lrJ9cbkHABlMZbQskUKGAY1y5VBWg2V0aMfGhJsZKtCAgmlkgnY0gmlwhKI3BbaJc2VjcDI1NmsxoQP9bM-ylBjwT9ujG09A__0to5F3Qw4QSvY4_vgyQ0A7j4RzbmFwwIN0Y3CCVTyDdWRwglU8"); - txtRecords.put( - "D4GT5QUSIVDVPURCUOLE4WITZI.all.holesky.ethdisco.net", - "enr:-J24QAgjCyFyNsDJHJwpo6uy0l7yqJD5r6WF58M1T7_PqsyKC0t6y1iTsQX4Crz6iv5Ijc8LqPZIC6vhA_F5GqkdkgSGAYtfbIIbg2V0aMfGhJsZKtCAgmlkgnY0gmlwhCvIu-OJc2VjcDI1NmsxoQKq0Xs_18RfgehL1rawMx2B3VMaH9d-YNCY5kmHuaCnqYN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "O6EQKGUJOO2BQGMADYZHOSZXLA.all.holesky.ethdisco.net", - "enr:-KO4QAg_or5YgVU8ScSgcgvNmLMISW0LA4L5GtRLxmyVUlGRR4GNuEZ9q_tKtZdAbLH5B-FN2ie8hp0U6P90d39xtyWGAY3d9Tplg2V0aMfGhJsZKtCAgmlkgnY0gmlwhAMRsEKJc2VjcDI1NmsxoQJ7STjsgZvt1OOj5krr2l2iAwlY5AGl-dgTziPZsCd9qIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "WRRS22MCKTZ4YS3ZLPUJEUAHUI.all.holesky.ethdisco.net", - "enr:-KO4QG6_DN_yg3WbcE7jc_tGXK9dLJPplfz0cbzCRwT8v5o6BBk_e_ER73cttVRyYkkvBDyw-akwTHYxjCe2Jp7OWuiGAYrbEiVVg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEV49OJc2VjcDI1NmsxoQKYHwvu1PazCFKpleCUgYvxialsHK6_iLwC_R2i3J70fYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "3A6T36TYZJBFLOHMJOJGL5SPXA.all.holesky.ethdisco.net", - "enr:-KO4QEVn1fwLQuOHSsRVS6bBIv7Nhr39Ze03wqUhwdgEn0IUeEB9TZAx6d9E-C0tBkQdEnbwld1YxEsB9gRY0Od13nGGAYzxMuKBg2V0aMfGhJsZKtCAgmlkgnY0gmlwhAW9up-Jc2VjcDI1NmsxoQKNkgm76tKKtumISB2-1oGEHv_mZtMVh7JGrSmvlX5qqYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "XPTWTOASNO4WEBXH74WCQ5EYTQ.all.holesky.ethdisco.net", - "enr:-KO4QEk3I50HU8omZLCYgSwXs1caXS-q06lC_GnJT30gbzlIEDWBldaoC83sg9aWTQ9DdkQStosOuYEb13gxTwp4tSKGAY5DOiDfg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEVX7SJc2VjcDI1NmsxoQPCijWe7yQtKeDfuj6WXiJZ6CZ88FGXKj6DGSWdq0E0qYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "2XMYGJOCRIHHMHAFVRH3OES5QY.all.holesky.ethdisco.net", - "enr:-KO4QAMrUvclx-EHZrO6x4K6fWTwaKh2zS5oWJzVU6oZuzZ2GG5xdQky8tIrpUUSKGB6XGvr8TvmiS7Dimvxx9Hb1OOGAYy06xQvg2V0aMfGhP1PAWuAgmlkgnY0gmlwhEFsSLGJc2VjcDI1NmsxoQJiz0q4ywyhHvmXBrWbFykzflq_J6cmLFIbepO9PLBwhoRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "RWRV55FT3DKDVGZK7AEU2DR77Y.all.holesky.ethdisco.net", - "enrtree-branch:ZV5CYUMJPF4XSTFHOG65622KEE,QDCV4SQTQAGHHA3DRA2TR4LNMY,MZGQKIQKIOFO3W2GJURJUXVYMQ,V5LTO36DCXU2JCCSSJ76E35OFU,NQHD4WNRRGE5JHHX6IDKGIF7KU,ELYMU6HPEMGLIYO2UO4YNUI7CQ,VN7EWW4RMUZM6KUOQ43GJ33DWM,J7ZIDKH7CKVIYY244QVU6RMPDM,QSK67XVHDPEQH7Q54HACQS"); - txtRecords.put( - "ZV5CYUMJPF4XSTFHOG65622KEE.all.holesky.ethdisco.net", - "enr:-KO4QDd2Ia2HHxIk_v5zyXOs6LIkR7a3wVUmipLZnQTJJMZtTNVY5PnRonxr_GAQ3nqG5R8f-eiyoYprxN1XjsfRlnyGAY2jAkZPg2V0aMfGhJsZKtCAgmlkgnY0gmlwhJ7cbM2Jc2VjcDI1NmsxoQLx82Y08wCtO72z_znVjHxPl3hBz1YBlPokUpsLFKRHJ4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "QDCV4SQTQAGHHA3DRA2TR4LNMY.all.holesky.ethdisco.net", - "enr:-KO4QFVXTftQWqwggnKZlVzFmnQz-U5jzIx37kQSltdvU5kuYQHT1mboaEBh92lS0BUvn-aBzLmHu7wX6hhs2xdGw-6GAYrk72-Hg2V0aMfGhJsZKtCAgmlkgnY0gmlwhCJSHduJc2VjcDI1NmsxoQNGPGIxN3h_R3TiUe0Ud2WazTigmsK028DXXuVLna9rtoRzbmFwwIN0Y3CCdTWDdWRwgnU1"); - txtRecords.put( - "MZGQKIQKIOFO3W2GJURJUXVYMQ.all.holesky.ethdisco.net", - "enr:-KO4QI7ESDG0rx7lEU_FFFFkALgNr9roSDrKDOzmEdxTHwsxKj5ozskjEjwFPrShowZbzTTYCp-NRhhw3uhcmKCYy0GGAY1lomwig2V0aMfGhJsZKtCAgmlkgnY0gmlwhCUbQPGJc2VjcDI1NmsxoQMV_yyKYF25xvgoJARC1swzb0S0llZJEmMLRHoINSR81YRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "V5LTO36DCXU2JCCSSJ76E35OFU.all.holesky.ethdisco.net", - "enr:-KO4QMLA20nV5gEPR5ugJWkqHSkU89wLsjSVlpdbjuXGueHRY_VJ7gfCBuPbnyxN3Y0rYJThJU7b7IAA1yBfcRx6Uo-GAYrbDOlPg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKGuJc2VjcDI1NmsxoQNZKgK-Lr-g81vUAH_bV24PDyOwNQaNPgAOBEA3qV3JxYRzbmFwwIN0Y3CCdnSDdWRwgnZ0"); - txtRecords.put( - "NQHD4WNRRGE5JHHX6IDKGIF7KU.all.holesky.ethdisco.net", - "enr:-KO4QMTq7zJT6fLS-LY7n3hWmcwJqxRJraGJcM60U9Cej2lRRE-u_KhOd9r712IBEOI39uiVQEXGdBZvc-dJIESUcOOGAY2dUsseg2V0aMfGhJsZKtCAgmlkgnY0gmlwhFD5eBSJc2VjcDI1NmsxoQO-UyE5ad7DaM-f7fZi4QEwjfxIR6HS-l-lTSlv4ZLdB4RzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "ELYMU6HPEMGLIYO2UO4YNUI7CQ.all.holesky.ethdisco.net", - "enr:-KO4QOWLzzuDHlwrEZhdOmeHEuFi87mX6iwJ_V820RsBL0mCLngiBaLbhumS1Q8zIc6YPzCYN07nigSMr-OXf19z3WSGAYra1TQYg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKFGJc2VjcDI1NmsxoQO4QASxyNUTDVIRkvEDQejetqJ4roUtGDD6U8CoI9wm-YRzbmFwwIN0Y3CCdnSDdWRwgnZ0"); - txtRecords.put( - "VN7EWW4RMUZM6KUOQ43GJ33DWM.all.holesky.ethdisco.net", - "enr:-KO4QI3OPqietyANJtQ9_RUOU6ELbwDgTn39MufjLJ9BEzkwTxEgDcgYekeisrchGWzrzTC8T_2zowbuQhZlTkqNXB2GAY39ItF5g2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFsBbiJc2VjcDI1NmsxoQLa2k_Jy-sjrZ-0305yX_F1ZxDNNfCKIImoL5tyDN3cEYRzbmFwwIN0Y3CCdl6DdWRwgnZe"); - txtRecords.put( - "J7ZIDKH7CKVIYY244QVU6RMPDM.all.holesky.ethdisco.net", - "enr:-KO4QN942TWYsHn5c2-vZhoeXwELvI2zVg2eHk6nzoyTN8Cwcb8n-4_cwQPIU5p7DsuOehtu-raiGxYm4vE_6WUhT6yGAY1ClkGgg2V0aMfGhJsZKtCAgmlkgnY0gmlwhJBbb1yJc2VjcDI1NmsxoQKemKJgEuvyzZofyTm82rbw716cyTA2hf6zSJ73kzl_ZIRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "CTDMY3ALJA7FRKVS4MCTAJRRH4.all.holesky.ethdisco.net", - "enrtree-branch:UIGSNQMHERWJIZCP2OLUXSZ3KM,I56MJYJBMXTZZEPBQR6HWNAH7A,IZNO6JEFTYHQWWF3LJ2M66P7II"); - txtRecords.put( - "UIGSNQMHERWJIZCP2OLUXSZ3KM.all.holesky.ethdisco.net", - "enr:-KO4QEvn7KsE7UDJ5F3HwtSmGNSy0JSYDLfp9uJRtq5WfSsuOdkfq3V7GVSDo7IfCTXYAtQfRwT9q88II0sajv-m_CKGAYulV6JLg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMwQ9ISJc2VjcDI1NmsxoQNVsiSzpuxjNDgNToi4u48-5kCgvsjuoUsvlG9VrnEjhoRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "I56MJYJBMXTZZEPBQR6HWNAH7A.all.holesky.ethdisco.net", - "enr:-KO4QCTGqfxu7eEfe6M83e3bQLUgkKWGCdv6Ib5D0gCuQkkvbq1D0CN8UmHgpsddZNKHa2iBl3EIiGhaLBs_NKTxGQuGAYranLv7g2V0aMfGhJsZKtCAgmlkgnY0gmlwhCKH3xKJc2VjcDI1NmsxoQMHuEvotUinJNm_8Vz7412P1yKZ6r5iz9EcYj-9v3weRYRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put( - "IZNO6JEFTYHQWWF3LJ2M66P7II.all.holesky.ethdisco.net", - "enr:-KO4QLmjLLs27xB9n9N2TNC32EhbxjdiR32rroemDty1dQJsBSZQOawi3-HGqWDRifXce49pv2WewtuzF5j54iiUfbaGAYrbEiX_g2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEV5riJc2VjcDI1NmsxoQO4pbsV79dSOLkBaOX5vcv6Amy9rH2xg1wW3OsKxVA7PoRzbmFwwIN0Y3CCdl-DdWRwgnZf"); - txtRecords.put("FDXN3SN67NA5DKA4J2GOK7BVQI.all.holesky.ethdisco.net", "enrtree-branch:"); + + try { + final Map dnsEntries = + new ObjectMapper() + .readValue( + Resources.getResource("discovery/dns/dns-records.json"), + new TypeReference<>() {}); + txtRecords.putAll(dnsEntries); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } } /** Stops the mock DNS server. */ @@ -584,7 +195,7 @@ private byte[] createTXTResponse( dataOutputStream.writeShort(1); // Class (IN) dataOutputStream.writeInt(60); // TTL (60 seconds) - byte[] txtRecordBytes = txtRecord.getBytes(StandardCharsets.UTF_8); + byte[] txtRecordBytes = txtRecord.getBytes(UTF_8); dataOutputStream.writeShort(txtRecordBytes.length + 1); // Data length dataOutputStream.writeByte(txtRecordBytes.length); // TXT record length dataOutputStream.write(txtRecordBytes); // TXT record data diff --git a/ethereum/p2p/src/test/resources/discovery/dns/dns-records.json b/ethereum/p2p/src/test/resources/discovery/dns/dns-records.json new file mode 100644 index 00000000000..e51da66dbb7 --- /dev/null +++ b/ethereum/p2p/src/test/resources/discovery/dns/dns-records.json @@ -0,0 +1,137 @@ +{ + "all.holesky.ethdisco.net" : "enrtree-root:v1 e=QXOF2GWVHBKMKW57Y2KSKWYNFQ l=FDXN3SN67NA5DKA4J2GOK7BVQI seq=932 sig=DuA35BkYo9-FBwJ6MPxdNnYfcGMSGunAKUyfNN2gYQhYDBCPFZkr_cfe40Wspl2Vl76w6Ccs-B8ZrXpI_YymrAA", + "I56MJYJBMXTZZEPBQR6HWNAH7A.all.holesky.ethdisco.net" : "enr:-KO4QCTGqfxu7eEfe6M83e3bQLUgkKWGCdv6Ib5D0gCuQkkvbq1D0CN8UmHgpsddZNKHa2iBl3EIiGhaLBs_NKTxGQuGAYranLv7g2V0aMfGhJsZKtCAgmlkgnY0gmlwhCKH3xKJc2VjcDI1NmsxoQMHuEvotUinJNm_8Vz7412P1yKZ6r5iz9EcYj-9v3weRYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "IPSUCB6CICZIW6SERKZL7FFTIM.all.holesky.ethdisco.net" : "enr:-KO4QD47Vx9bnFcvE6JNAhrLz6GGgPk9GaJtP95ogjSOFHxlcZjnrT5TQzW9Jyx99XO9rzhK6WSvgdFoA9_54QPQcUaGAYxBy9opg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIe1TLKJc2VjcDI1NmsxoQKMO6bMfiH5DnJNFZgSqUEi6uHNnH2_bMh7hwVXRWTMl4RzbmFwwIN0Y3CCdqmDdWRwgnap", + "QJM2RVJT7USPDSI5VVFGGDW6JM.all.holesky.ethdisco.net" : "enr:-Ji4QHWntDnLOi4S9GKmF4HY7WrnJuJGaIBrnM3Y75G-8EUpZExnUkaZJtlmzOeJs6WAcYsissKSybUG5rSEiad0s86ByoNldGjHxoSbGSrQgIJpZIJ2NIJpcISLY0UEiXNlY3AyNTZrMaED5hLZweFA_nvWEc-Q7Vxqf2lIYxScF-F4ORxs-rVyHZKDdGNwgtn5g3VkcILZ-Q", + "C7SDD5OPASV7B2XUOXF5NLBBKU.all.holesky.ethdisco.net" : "enr:-KO4QLo95TIKo_axZA9xafYgl9jQ2ZTDFCQ29znjUwp-6zPUe6o8L9NO3X3n3uMZH8bFGnRmoIhyHLxNoOp2BU6O7xGGAYwoJEiug2V0aMfGhJsZKtCAgmlkgnY0gmlwhKEjElWJc2VjcDI1NmsxoQPIffRacW7k1QukSBfJ8leopO1M5wOv89G06m0SAQDB8oRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "AUQGHXRAP7J3AGI6TUOQVYTYLY.all.holesky.ethdisco.net" : "enr:-KO4QGuiAZHlxH7T5XPcPpRTovca50B0pzDLkgJ-zaxkjT_PO6zsAWQezB-b53yAnqx01Jdj5tQWsvWoaNVfiFw3iw6GAY1nSgXzg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEp2jJuJc2VjcDI1NmsxoQMTiH7XteYOVAHiXAZoacUjo5JYLhQINeFMbPbEY9sA84RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "WLME2JINTIQACAMD5WZUQ4PRJI.all.holesky.ethdisco.net" : "enr:-J24QIMYTLuDOO1F5w8AL_WGXU6CZmzi05W35Rz8PuVT8b6vUqmUatifQtzuL2Q7REuRY5kYNurUbLHscGx3j1zjTmmGAYrbbtBig2V0aMfGhJsZKtCAgmlkgnY0gmlwhETp6VKJc2VjcDI1NmsxoQKrwy6w-9JGp3dagVeXMZEd3iZGff-rEVTqd4gmkhBRBIN0Y3CCdl-DdWRwgnZf", + "5JAERI2BPJN45X7IK6SWZOZ2FI.all.holesky.ethdisco.net" : "enr:-KO4QEsCIXXCDLUsTbUOT3ILg3rpga0gZXaG9_YT9_kFk97iRwu_Pr4RlhDvdkg4Y4bl9SBVzP9bFtL7H9AU7QxE7OeGAY1lbo59g2V0aMfGhJsZKtCAgmlkgnY0gmlwhA3Xl1-Jc2VjcDI1NmsxoQPsZi79eOcjTI3Xa2GCBiXRCgtm7wFo19cVlmhQSZ-i64RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "J7ZIDKH7CKVIYY244QVU6RMPDM.all.holesky.ethdisco.net" : "enr:-KO4QN942TWYsHn5c2-vZhoeXwELvI2zVg2eHk6nzoyTN8Cwcb8n-4_cwQPIU5p7DsuOehtu-raiGxYm4vE_6WUhT6yGAY1ClkGgg2V0aMfGhJsZKtCAgmlkgnY0gmlwhJBbb1yJc2VjcDI1NmsxoQKemKJgEuvyzZofyTm82rbw716cyTA2hf6zSJ73kzl_ZIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "FFM67SMD4N3SK4ZQTY2HOMU4F4.all.holesky.ethdisco.net" : "enr:-KO4QG4O1MBSzBHtShtqoXXeAycBqcDpuBK2BBwIuK_Qppz5OpJC6Ga3-qDEQJQOtbEYm0yXMhenb50Get_H6Bu9SbGGAY16lghqg2V0aMfGhJsZKtCAgmlkgnY0gmlwhA_rV76Jc2VjcDI1NmsxoQJ1vrJ4fwXiDAhSqIJwbELp2ktd9A0zRLws1li91-aBmoRzbmFwwIN0Y3CCdmCDdWRwgnZg", + "GZVG4EONSAY6M7SMLJXDCFUF5Q.all.holesky.ethdisco.net" : "enr:-KO4QErAUNKF003oZAYn8UUocPIuq2MTpxHV3gHfrNkThYPbZ366m-mlvXezUX5JDA5iJE-LTWWEPh8JqjEDah5kVimGAYxA2nMcg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFtWv2Jc2VjcDI1NmsxoQMCgMWZQXkPyMKdwnzdBxQe6rcs25guZtULvfzDjhE71oRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "3AVV65MYCJ7AGFUCKXT2UX2DSQ.all.holesky.ethdisco.net" : "enr:-KO4QEaesvQ1kySNS_mnQfiBr4DZQmkhHmqKEGRQvgyd5K3lNDm3vjs9cMMVTY9FgENpP1o0UGBsapNNfvXIN2gaudGGAY3ulUKIg2V0aMfGhJsZKtCAgmlkgnY0gmlwhDIjTkmJc2VjcDI1NmsxoQNVbUX0SvjLX7d7psFtTTNkKT7x_zHl3f4bSVQrkbK1YoRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "QDCV4SQTQAGHHA3DRA2TR4LNMY.all.holesky.ethdisco.net" : "enr:-KO4QFVXTftQWqwggnKZlVzFmnQz-U5jzIx37kQSltdvU5kuYQHT1mboaEBh92lS0BUvn-aBzLmHu7wX6hhs2xdGw-6GAYrk72-Hg2V0aMfGhJsZKtCAgmlkgnY0gmlwhCJSHduJc2VjcDI1NmsxoQNGPGIxN3h_R3TiUe0Ud2WazTigmsK028DXXuVLna9rtoRzbmFwwIN0Y3CCdTWDdWRwgnU1", + "366Y3UIIKK2G5CUS7CHNPZMPEM.all.holesky.ethdisco.net" : "enr:-KO4QK1ecw-CGrDDZ4YwFrhgqctD0tWMHKJhUVxsS4um3aUFe3yBHRtVL9uYKk16DurN1IdSKTOB1zNCvjBybjZ_KAqGAYtJ5U8wg2V0aMfGhJsZKtCAgmlkgnY0gmlwhA_MtDmJc2VjcDI1NmsxoQNXD7fj3sscyOKBiHYy14igj1vJYWdKYZH7n3T8qRpIcYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "LHFO3FXNRHB5B3YSTKUALGMEAI.all.holesky.ethdisco.net" : "enr:-KO4QEuoZibAJnKlhcAENpJiju6nVH5J2or_xnVnX_eputCuTHBVtNJmoRKgDBU0idG4kbxFPPKaWRQ3TdYeIbsAAV-GAYrax-PMg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKIOJc2VjcDI1NmsxoQNCYX5kXJqI2deaVOtsTmUQka7dIocapHSAfnNn_CTL2YRzbmFwwIN0Y3CCdmCDdWRwgnZg", + "3PMAED3BC2HUS7AMEPGJDKENQY.all.holesky.ethdisco.net" : "enr:-KO4QFMsKiZk64UQsyLVHrxTpTpmdZYzuI-Xgy0gut5NgLulRvbrgQDmdrAT2PQuxK8K9AiplRzJ_i8CtseBD_d2bwGGAYx9HQirg2V0aMfGhJsZKtCAgmlkgnY0gmlwhLBn3miJc2VjcDI1NmsxoQJSdj6d-7J6YsK6vZwRl_zzMSCKauLadYxL8zfjjzOQZYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "4PFOQY7RKTNLRPXQXKKXD3K6LY.all.holesky.ethdisco.net" : "enr:-KO4QJ8BuQCt7gBXU_26FNx8waHuCzCkG54mErQoBAJrYaw_bdUJT0yQA0vG8gr-dUoS77OX0D4tD-R6e30832ncLMaGAYrbEjQ3g2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEV4tiJc2VjcDI1NmsxoQJHEsWJ3CoSi-1JgkeSogpyRqYvBaJI77-soOcbmB_nyYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "GUBLA7OPRPBMYMQ76EXDMB7BHY.all.holesky.ethdisco.net" : "enr:-KO4QIFA0MiYPPyzvlUqi5j1dL1RGz6MdFhhLN30iXeNc1JfCS1QqTJciby7ZhlQYwFdVMuk_ptgu530WxiQR-UmZpWGAY2ia8cWg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEt3l4OJc2VjcDI1NmsxoQJRcQCiMhxUex-gtsxf2IWJ6nGita_F8BaPZxTwE310WIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "VN7EWW4RMUZM6KUOQ43GJ33DWM.all.holesky.ethdisco.net" : "enr:-KO4QI3OPqietyANJtQ9_RUOU6ELbwDgTn39MufjLJ9BEzkwTxEgDcgYekeisrchGWzrzTC8T_2zowbuQhZlTkqNXB2GAY39ItF5g2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFsBbiJc2VjcDI1NmsxoQLa2k_Jy-sjrZ-0305yX_F1ZxDNNfCKIImoL5tyDN3cEYRzbmFwwIN0Y3CCdl6DdWRwgnZe", + "FDXN3SN67NA5DKA4J2GOK7BVQI.all.holesky.ethdisco.net" : "enrtree-branch:", + "QDMF4HIR2UEXRJVOX4UJPJTPKA.all.holesky.ethdisco.net" : "enr:-KO4QCeMBK9Ur5AHbkdov7TdALRsHzc48RXNtvR861fcmcxPYH59PthmWiT_pQHfVOH3x9-HK2bG8_h2jm46sZyJ6UeGAY2ZCasag2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFs7nOJc2VjcDI1NmsxoQLT22lM9abRweZb93jeMd4jvuRsXnjQFT2oY5a0rb0cvoRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "3JSDZJCCWKIEBGPH5EI5VZF4RQ.all.holesky.ethdisco.net" : "enrtree-branch:4PFOQY7RKTNLRPXQXKKXD3K6LY,3AVV65MYCJ7AGFUCKXT2UX2DSQ,5JAERI2BPJN45X7IK6SWZOZ2FI,YUZZDZD57PTNEIB5QL3FEXK2ZA,TMY2W2YBNCXUUNA3Y7QXWVQLRE,MJB42632KKOPGJ2KNY3GPKP66I,QXRV67JYJNOYJDPC5CTKUVG76M,QJM2RVJT7USPDSI5VVFGGDW6JM,I33RZWBACJHNDEXG2RRXMX", + "A63OP4WTCB3HGDZE4NGDEID6Z4.all.holesky.ethdisco.net" : "enrtree-branch:QXKEJG4XZEQSXNUY7JJYCATPEI,HIDVATDVB36L2MASAWA7SBJAII,CHFGCI2RQS3XFN2MKFP6G2ZM4U,GSONRYZILMGUJEN3PYQXYD6GYQ,OSHAABXYJSRWW35VOMYHZUKJXU,RUJF27NEYDBNQAFMI6K6SKDVRY,TXAFU343ACQLSVIMT5LYG3W2AE,R6EQA5KQEQM77JJXB4BHHTDF6Y,CF25LCQ452FBTR24CZU4C2", + "N7HAL5M6HNZBGTWM3LWFDRX4WU.all.holesky.ethdisco.net" : "enr:-Je4QI2f2MAXFFi0Q3--GhXVZwNP-jI-G6XwmiDXxT-iobTTKOBJCyWUPk5WSoTtRS2ABqJmTAK8jflXBWLHuY7AueRFg2V0aMfGhP1PAWuAgmlkgnY0gmlwhDmAXJmJc2VjcDI1NmsxoQIc4YD6HsNIoj2HVJJDzr3cSfNtcEBRLtk5kgwXNH0q8YN0Y3CCXoeDdWRwgl6H", + "QXOF2GWVHBKMKW57Y2KSKWYNFQ.all.holesky.ethdisco.net" : "enrtree-branch:A63OP4WTCB3HGDZE4NGDEID6Z4,PL27G47LAASBPPAMXUDIJ3OCRQ", + "23B5UFB3JWETLIKE5Q6B2B5MWA.all.holesky.ethdisco.net" : "enr:-KO4QGjsmd6RDlGYHJu2JnI-Lf4TZ8s1yJtLDvvtp_Kw6xIwNn7_Ti_FmEHfWbaSyy0Icl4jlErvH-LrEmi9PCzB27uGAY0MEI03g2V0aMfGhJsZKtCAgmlkgnY0gmlwhNXvxn6Jc2VjcDI1NmsxoQOzxdn7S7O58IiaWGk4n3MsiB816GGv_bAd_PM9FkWeZIRzbmFwwIN0Y3CCVTyDdWRwglU8", + "TXAFU343ACQLSVIMT5LYG3W2AE.all.holesky.ethdisco.net" : "enrtree-branch:3EMD3PTQYRFVNFBQ73BYFL4AEQ,PMLO73ISEK4Z6IRPOSWIUUKF64,5NHWEAJHKSAO5DIIYAZ7MQJVSE,LNO6EN3Y2LJA5YOJMEMODAJFWM,23B5UFB3JWETLIKE5Q6B2B5MWA,HQ5S2EHH762PSKVTHWNQ6346NI,O25DXMPE4UEZ6SFMQRBGL2E2I4,2WHIXLN5QX3JEK3SSH7MXKTJMU,WQDLDQ7U74Y2HM5LHVCOCY", + "PMLO73ISEK4Z6IRPOSWIUUKF64.all.holesky.ethdisco.net" : "enr:-KO4QBxZ1JFNQEopzO-wMumFIw4fGHkuPZkuCLvgcz0X8L8yKWUkQ-UiS7-KHBCjGOby7yjR6m7m9dqoQvlHejBncSWGAYv2B0KZg2V0aMfGhJsZKtCAgmlkgnY0gmlwhA_ML-SJc2VjcDI1NmsxoQMEi6SKsKUVTnxDWKc1Go6ZG8o5nEapNJBaWAd_YC26R4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "CDXRYZFKH6OUWTA2IJ2MYIMYPM.all.holesky.ethdisco.net" : "enr:-KO4QExy2s5Uh2eTEJtsODBEOcPZZN-CYY0WEr_8nA_uK43QV3iqmFzfXJ-29zootm5F-E-DrjzTObp2tw_klTXPRu-GAY2ZtbV8g2V0aMfGhJsZKtCAgmlkgnY0gmlwhBJ2VQmJc2VjcDI1NmsxoQJuk1X-4JUVQT-mguV7dSGSUC-RoxdnTyOmD-VrfRFQyYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "CTDMY3ALJA7FRKVS4MCTAJRRH4.all.holesky.ethdisco.net" : "enrtree-branch:UIGSNQMHERWJIZCP2OLUXSZ3KM,I56MJYJBMXTZZEPBQR6HWNAH7A,IZNO6JEFTYHQWWF3LJ2M66P7II", + "GSONRYZILMGUJEN3PYQXYD6GYQ.all.holesky.ethdisco.net" : "enrtree-branch:V4A7I7I2SFVUDFFH7AGPFXACBA,67FFSS5IYEYRGUYFEWGIJDIMII,NXU6DU7HEM2L4DN6G2VHHFT72I,3T4CNEKYCJP2PJ3EQVJSFKHH44,KYTZHQX2PSOGY6RQTCVTUKTS7E,GUBLA7OPRPBMYMQ76EXDMB7BHY,HGROZTZI7YPDW5F37QVUPQEBG4,AB27AIPX5Q5J6UAXQ6C2QFTYZA,7UIVPNORDS3RPPTLSNGYAN", + "IPU725BUGL4HIOTSOIH3KZG5ZA.all.holesky.ethdisco.net" : "enr:-KO4QLf3R97a5p3pRgY1AeN1DisWZUTnHbIMt2aPh0zvq8QIZguS9EiujNa71YXqJgxw7ztz3YmwJMWuPuhfWqSkxqWGAYrayxVYg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKIOJc2VjcDI1NmsxoQIHu5vOuFNLO8814yybwdh03yUQzfPTTyeK_X44Gz5jx4RzbmFwwIN0Y3CCdn6DdWRwgnZ-", + "V5LTO36DCXU2JCCSSJ76E35OFU.all.holesky.ethdisco.net" : "enr:-KO4QMLA20nV5gEPR5ugJWkqHSkU89wLsjSVlpdbjuXGueHRY_VJ7gfCBuPbnyxN3Y0rYJThJU7b7IAA1yBfcRx6Uo-GAYrbDOlPg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKGuJc2VjcDI1NmsxoQNZKgK-Lr-g81vUAH_bV24PDyOwNQaNPgAOBEA3qV3JxYRzbmFwwIN0Y3CCdnSDdWRwgnZ0", + "2SA643JTHJ75ZVPFHAKLXI2WXQ.all.holesky.ethdisco.net" : "enr:-KO4QEPFY3aDBt5F3VQU5qcGZED8Xr-J9v1Fi4CcGpx7q_fydrJM8h0RqAXSdcJbr03b7ysPaOR4mNUTRou4kjPsu7yGAY4QKOX4g2V0aMfGhJsZKtCAgmlkgnY0gmlwhCO4xmuJc2VjcDI1NmsxoQKaOskToHERfimE_ei_VPRsh3fimBlYooVb80PVt0k-aIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "V52J3WKNVGG56JFRKYDLSZREJY.all.holesky.ethdisco.net" : "enr:-KO4QMC1Y7Fl44QyBr8KDFTyd1IQ_h0w67Igms87RDJeZJuXbc74aIVNkOeK0t3_zEJAT2spttSVu8tKDeV-cdiPE42GAY3KEqdCg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFsGDWJc2VjcDI1NmsxoQN24zvy5pQRtLA5_iKLgnTwXjI3T1KldFTOyp7Dk6dCRoRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "3PYSNKXWWSRBWV377EFNYINHSA.all.holesky.ethdisco.net" : "enr:-KO4QODvXk9lfhLNmdyttodPJQOIYQM36Rl9OZsFBZ4vMjaJcoJUXxTQCMqGLNTWmFh-1oEy0XoYKgPy4tywLZZcB8CGAYsolPILg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIe1OV6Jc2VjcDI1NmsxoQPj1f0OOW-g_vRGcihewV9-kcsQZdwXBHy4r8vwinurzoRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "ZV5CYUMJPF4XSTFHOG65622KEE.all.holesky.ethdisco.net" : "enr:-KO4QDd2Ia2HHxIk_v5zyXOs6LIkR7a3wVUmipLZnQTJJMZtTNVY5PnRonxr_GAQ3nqG5R8f-eiyoYprxN1XjsfRlnyGAY2jAkZPg2V0aMfGhJsZKtCAgmlkgnY0gmlwhJ7cbM2Jc2VjcDI1NmsxoQLx82Y08wCtO72z_znVjHxPl3hBz1YBlPokUpsLFKRHJ4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "DQYYUE7KVEXGEYBH4V2QHI6P7M.all.holesky.ethdisco.net" : "enr:-KO4QBhk9j1k2tfVrwV4yVRS9jn2zwS9KKADlhqvMsZsqdXFZiz0s-vCAnqtkDfszY9peGDBiWulLdNe6KFuvqgxgy6GAYtHL5rAg2V0aMfGhJsZKtCAgmlkgnY0gmlwhDmBAQqJc2VjcDI1NmsxoQLXrL6FtBz248tpC2_DxJs2HsvtHdSoNsZIBwth_MMu94RzbmFwwIN0Y3CCdmqDdWRwgnZq", + "MZGQKIQKIOFO3W2GJURJUXVYMQ.all.holesky.ethdisco.net" : "enr:-KO4QI7ESDG0rx7lEU_FFFFkALgNr9roSDrKDOzmEdxTHwsxKj5ozskjEjwFPrShowZbzTTYCp-NRhhw3uhcmKCYy0GGAY1lomwig2V0aMfGhJsZKtCAgmlkgnY0gmlwhCUbQPGJc2VjcDI1NmsxoQMV_yyKYF25xvgoJARC1swzb0S0llZJEmMLRHoINSR81YRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "MJB42632KKOPGJ2KNY3GPKP66I.all.holesky.ethdisco.net" : "enr:-KO4QEBK_p981nqXnme4a2sYvHW2FOz_OwLwstllVRvE8w-dVnDBrHxVZg7XSQwseErDQpfUgYMnBPMzpsx_bDUui5eGAY09OSoRg2V0aMfGhJsZKtCAgmlkgnY0gmlwhLzWgw-Jc2VjcDI1NmsxoQM2fE-8xAAaBUMu7dwviv_y8osp0rYMaKtaGBnbUba_J4RzbmFwwIN0Y3CCdl2DdWRwgnZd", + "QY2XP6FTX7QKFNP6TNZ2MOHZKQ.all.holesky.ethdisco.net" : "enr:-KO4QFsIg8OnqK5HJhhCaEuUTJ7RkLcTAXnUOOcaK63Ra2Kya2HsYSRgM1jRyr9utt3ib83q4fsU_IOg7kuR1jf2u2SGAYray1Tfg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKI2Jc2VjcDI1NmsxoQJiD81UDxWESi3paPaopKxnoF5vf0GAxlroaPr6bSIykIRzbmFwwIN0Y3CCdn6DdWRwgnZ-", + "3A6T36TYZJBFLOHMJOJGL5SPXA.all.holesky.ethdisco.net" : "enr:-KO4QEVn1fwLQuOHSsRVS6bBIv7Nhr39Ze03wqUhwdgEn0IUeEB9TZAx6d9E-C0tBkQdEnbwld1YxEsB9gRY0Od13nGGAYzxMuKBg2V0aMfGhJsZKtCAgmlkgnY0gmlwhAW9up-Jc2VjcDI1NmsxoQKNkgm76tKKtumISB2-1oGEHv_mZtMVh7JGrSmvlX5qqYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "WRRS22MCKTZ4YS3ZLPUJEUAHUI.all.holesky.ethdisco.net" : "enr:-KO4QG6_DN_yg3WbcE7jc_tGXK9dLJPplfz0cbzCRwT8v5o6BBk_e_ER73cttVRyYkkvBDyw-akwTHYxjCe2Jp7OWuiGAYrbEiVVg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEV49OJc2VjcDI1NmsxoQKYHwvu1PazCFKpleCUgYvxialsHK6_iLwC_R2i3J70fYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "BAY6SKB2RTQCM7RTSHAFQ6TTBQ.all.holesky.ethdisco.net" : "enr:-KO4QIo--Wic2Dyk2II8x2uTPNOFY3CRZ8wmABP5qOhmZv4LXvAfbdISPp7HDrJwrejs032LHH5rhUY5l8bj9-M-_u2GAY3GDGQWg2V0aMfGhJsZKtCAgmlkgnY0gmlwhI6E2M-Jc2VjcDI1NmsxoQM46NJn0fjSRwgGkB0G1n7QQ9VyBDwiPXPX_xgM25tDOIRzbmFwwIN0Y3CCfEyDdWRwgnxM", + "5AOSND63QKPVT6EWNMALKAFC4I.all.holesky.ethdisco.net" : "enr:-KO4QKn6DlOQ0ybfLAfyPlyPssuWtP0Zu5FEUEgr3015XppiCP4yr9SeMgnpN90AVHJA5C61F3677GiO0N-JIXGfN2uGAYra2k7ag2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKJOJc2VjcDI1NmsxoQKgKJovbRS4hL3ugMVrevOCUGDS-ixgByq_tbh4T9oihIRzbmFwwIN0Y3CCdmCDdWRwgnZg", + "PL27G47LAASBPPAMXUDIJ3OCRQ.all.holesky.ethdisco.net" : "enrtree-branch:3JSDZJCCWKIEBGPH5EI5VZF4RQ,XTJ3PFTPBB3ATDDWAA6W7RRKMY,FTTHOFPRPIHMJNZA3VZUAW5TIM,DAVKEW6RQ5YTYHKCJ2A2J5GVCI,TGKURK5IPVCPXS4QPS2KZJCP3Q,RWRV55FT3DKDVGZK7AEU2DR77Y,CTDMY3ALJA7FRKVS4MCTAJRRH4", + "HQ5S2EHH762PSKVTHWNQ6346NI.all.holesky.ethdisco.net" : "enr:-KO4QK883RN_CIppuOvhzi0O3Qg-XzHUZsdKIEqLdvZMLN5BCV5QTqqxRzJjtXa7NZjBi22UQJUUlsrQ_3g3Uavv92aGAY1BRdWag2V0aMfGhJsZKtCAgmlkgnY0gmlwhFJkOnSJc2VjcDI1NmsxoQNAyMtRruAQcNn10ZReZiiZtPnk35o_oJEhabuBKi1gLoRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "XPTWTOASNO4WEBXH74WCQ5EYTQ.all.holesky.ethdisco.net" : "enr:-KO4QEk3I50HU8omZLCYgSwXs1caXS-q06lC_GnJT30gbzlIEDWBldaoC83sg9aWTQ9DdkQStosOuYEb13gxTwp4tSKGAY5DOiDfg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEVX7SJc2VjcDI1NmsxoQPCijWe7yQtKeDfuj6WXiJZ6CZ88FGXKj6DGSWdq0E0qYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "HOXANHAMPVCFA2QR54VEMWCWPE.all.holesky.ethdisco.net" : "enr:-KO4QELWztCXUz9MCxD8zbh65aa6LwP3wubhdXJVdlyrl7QzFJkUXosDXndbQqtAT2qD5nXAFpBnz5MeUSZf_GfjkI-GAY16vTc2g2V0aMfGhJsZKtCAgmlkgnY0gmlwhCZhyOSJc2VjcDI1NmsxoQMGwfOevbfZOGp3bL_2AH1SBdx3zn2_l56uG_4CWDf66YRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "BBNHIHYBPXVQTDZQ4JXK4QYDMM.all.holesky.ethdisco.net" : "enr:-KO4QCMGWCueZIoaBJyNAqOo_1wJ-QmbQ-qlTQx_rfz0gzKjF8VqFc99Q3ZMcfpK2qdLtWZHoRoiCkstH9_rWAU49DaGAYrbEkgdg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEVzHeJc2VjcDI1NmsxoQIy6rguhP2KemBUH2HJyW-GPo8prt6Ay9gjX3GugxLuGIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "LHFTDS37XFOEXQKTWJGKWRLFWM.all.holesky.ethdisco.net" : "enr:-KO4QB0RN-qZSymbzZA-AlKT8213I60xJjVVwN-AWwjW9W9aLk-2aq8jW_jbgKnJY3c_wU26oBzYFBpOVcYKIV_nd16GAYwlWhtPg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMPJ9bKJc2VjcDI1NmsxoQKlc1OTSAanfHEsPtrKjZvkgQUuHFZon2GtnCRHlDRV7oRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "67FFSS5IYEYRGUYFEWGIJDIMII.all.holesky.ethdisco.net" : "enr:-KO4QHY48cNs9Lauv6W3XCDcuBvhJbmL-0MAASBah_V-jG6iODMGVIZ9xv4K3MHSqQQht3Fc39CjU5zoYROe6D8uOq2GAY2UXNmdg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMb0yKiJc2VjcDI1NmsxoQNjc7k9S4kjTIwltOf7EH2ZmK3vZiEBpxxNqondpU3qV4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "MOBGCDYGQA4CNBQZDPN52JC5E4.all.holesky.ethdisco.net" : "enr:-KO4QDuar8SHEX1o_kUIKB0IajdUqcRb3ZkZdr5_MIxT1BUkLVk8AelLTv-_ioVGRAwNIJTzT4m8nyzV1VbsjjpeZ0yGAYrXy6Pag2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKEeJc2VjcDI1NmsxoQLy0OM9Ze8JjVHZhLQg7XejbI5iedlSDZNRmtQdTy_X8oRzbmFwwIN0Y3CCdn6DdWRwgnZ-", + "ERETBCE2BAPJRSWIEUQV3QPP7M.all.holesky.ethdisco.net" : "enr:-KO4QJY9GUtSCCXanmC9p7LYAfqk7jOUF-zxl7zd2ce5lD_aSNk9KfzBbh-Hii3qMzv-2x4a0Xal7RQMzQv9BqCkUMeGAYw1cGzgg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEVR7SJc2VjcDI1NmsxoQPQjk4qnGis3WKuSQDD20H-9n9xKFTbnLsB1DhAteyYk4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "5AHKPN5NN5IHEH365X6OYZDHDE.all.holesky.ethdisco.net" : "enr:-KO4QM-W42wfRYcTwaegJy3SfjlCkZsQU8LhGcluTdxtFjqXOwsEE5kUewkPMe7qIQpwb2MAo5xTeRpnijbdYTaG37-GAYtxMJdhg2V0aMfGhJsZKtCAgmlkgnY0gmlwhKh3IxmJc2VjcDI1NmsxoQNDrMpeyQI2aO_yoNP41RAf_BHtckptN1l2QIWxgDKOC4RzbmFwwIN0Y3CCdl-DdWRwgnZi", + "FEORTRPBZJSNRB3XCLK7KCMACQ.all.holesky.ethdisco.net" : "enr:-KO4QDO4oH5cjhncisJSk1SyGmwJ5VFjNetXw4OSSqDKc0NMDGXYYl6wUjbhcvzADYDpE1Br3lRG-1xV6iA1OAyXSAqGAY1W3CQQg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEVfkOJc2VjcDI1NmsxoQKkjEwDL0wuG0AH3RWw1wRrYHdPa8OOL1Ko4DYcZeQjQoRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "AB27AIPX5Q5J6UAXQ6C2QFTYZA.all.holesky.ethdisco.net" : "enr:-KO4QNZrQF9iyry46odExoUh3kkHjWWHS0iEEjc8n2R4Orq8Rov4Ar3ozsyWt9Z-dmBtpTfmAo3UYspLFcvtHNzwjPmGAYwWbywRg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFtbbyJc2VjcDI1NmsxoQLD-zctKWrecek1fu3o2IKHUY9rkd0BYeQHrzpqElk_1oRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "NHLNHXPRKOALUKH4MZ7CIVDONU.all.holesky.ethdisco.net" : "enr:-Je4QBgb-GKZRRjU2TAE_9KyBHC8ImLYDPG8KShRKx8gbKF3F-gpoHiyeprH42weMNSg6i8GeG-n0SxwWJRe6zyXj88og2V0aMfGhJsZKtCAgmlkgnY0gmlwhCV4sKqJc2VjcDI1NmsxoQNuVgNhx7pgtJotEBq56F9YFom-6szZ97gCkea35_lDkoN0Y3CCdl-DdWRwgnZf", + "XPX2T64BHDYLVGT5UC4GVIQIVY.all.holesky.ethdisco.net" : "enr:-KO4QMlMC7rvmM9tvHXvwo2Vj5x5FePlyvH_6lBSCgZHN8HcWqtppk1peQlK9Ge79ma4j3gx_zX0hgJvprIy1ehjXXCGAY3Mer4jg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIe1FCCJc2VjcDI1NmsxoQL3IIF3eENfSsr5tih2ebcm3dWL-otLdZYqUvfJ-5_9U4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "NZCRGKUE7ZXSIS4MIQ4WUS76BA.all.holesky.ethdisco.net" : "enr:-Je4QDqwqlm7UBywmVlR5UkkrQrg5B3UpkFexP7ucg4RAsdlMiJ4J1S5jT8jp6je6igMZ3OOnggdpd6l7QtQeNBICycDg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFs6LuJc2VjcDI1NmsxoQMgMreR2XspGJphg3fToxGKcMwWPE3e0gyxcqiQNLQXNIN0Y3CCdTuDdWRwgnU7", + "OF3GRGFZUAJTH3T2R2EXSYAZOU.all.holesky.ethdisco.net" : "enr:-KO4QDPbolwwOMTwV02zqyVgJFxko-HI8UWOT_K9sDcWgHG9fRDWkbSbTw_l3bcIA2-q3054lrJ9cbkHABlMZbQskUKGAY1y5VBWg2V0aMfGhJsZKtCAgmlkgnY0gmlwhKI3BbaJc2VjcDI1NmsxoQP9bM-ylBjwT9ujG09A__0to5F3Qw4QSvY4_vgyQ0A7j4RzbmFwwIN0Y3CCVTyDdWRwglU8", + "7QEPEVJFOCL5A63GZLZ4IZNMOI.all.holesky.ethdisco.net" : "enr:-KO4QNGh--C3ckyCMlUP4u86lt292CLHOQ3YIwk5Jqzz5x3OID_7BfRn5qN-ZBoBY80XOhFyHuo4eKDCIowQgYMvVY6GAY5S1aoSg2V0aMfGhJsZKtCAgmlkgnY0gmlwhFhjBqqJc2VjcDI1NmsxoQKZpCZSQgSun9pLzGrdfLboslJbD-JxtTsR4JF860eZYYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "Z5DOK3IB5X3IBOVVIK5MQCDYNA.all.holesky.ethdisco.net" : "enr:-KO4QJjEv14HV0BgdPwIa0iO-xrrwoi9k9LJRw3utIUVib5dF6s_b3y8z0NP2VyUGsJDFOwuVQq6OC6hb5_hFUqnf1yGAY12fbSog2V0aMfGhJsZKtCAgmlkgnY0gmlwhCU8-mOJc2VjcDI1NmsxoQIT2maaVlQBw4oD4loM1Q7gygn42pTQn1Sydo74LtZAZYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "CHFGCI2RQS3XFN2MKFP6G2ZM4U.all.holesky.ethdisco.net" : "enrtree-branch:XIOWDM2BTMANILG2M4LCMO7W3M,4LPHENNNENCUT7P3MWTB5IFRTY,5NQ4MKTYBPBMTGUT5IKUAXEGBE,CVK4DBIVPD2JPJOPQJQX77ZAWU,WLME2JINTIQACAMD5WZUQ4PRJI,DQYYUE7KVEXGEYBH4V2QHI6P7M,45V6KFI4NL43JFTJHL77KP6MQA,QDMF4HIR2UEXRJVOX4UJPJTPKA,BMVEIV6PNSN6RK5XEU4R4K", + "3EMD3PTQYRFVNFBQ73BYFL4AEQ.all.holesky.ethdisco.net" : "enr:-Je4QFiHIu4B3YbbG75mlr58JRaqpGUQij1n1pN3zaHoWbGrShq8aYqipbkPLaINPTX02YBfchyACgLHgH9cMarIUapQg2V0aMfGhJsZKtCAgmlkgnY0gmlwhDNRao6Jc2VjcDI1NmsxoQJHJlekJODLxBDPwpVLBtTMizLq5o9JtXkD8MQpqAIUYoN0Y3CC2fmDdWRwgtn5", + "IZNO6JEFTYHQWWF3LJ2M66P7II.all.holesky.ethdisco.net" : "enr:-KO4QLmjLLs27xB9n9N2TNC32EhbxjdiR32rroemDty1dQJsBSZQOawi3-HGqWDRifXce49pv2WewtuzF5j54iiUfbaGAYrbEiX_g2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEV5riJc2VjcDI1NmsxoQO4pbsV79dSOLkBaOX5vcv6Amy9rH2xg1wW3OsKxVA7PoRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "OA4ZFFGZNFUC3LIYCUDJOPVR6U.all.holesky.ethdisco.net" : "enr:-KO4QFuTcCcZkhGlEZ9YjHVURcwzRDKDu_o8RBID9eF5lmG9fb9ZHBHnz0E-tXO4TcPfwxEtefRAaAVPj9Ycdi3OeXuGAYraylBgg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKFKJc2VjcDI1NmsxoQJRTkcLLW6c3J6kd1b1siWyumgXACvQK7ViQjsgMFx0c4RzbmFwwIN0Y3CCdn6DdWRwgnZ-", + "ZCAW4EMBOL5EH4MSMW7FY6JMEE.all.holesky.ethdisco.net" : "enr:-KO4QHc4CzkY6KkiTGR6hTovOeJ8VJuUtjkq2-UMXwUijiKvAZOFVS_Nn_GHMVZ8Ppsu31rSLYAEFRu0tMJ-LIDdSjOGAY2sYroCg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIrJyaeJc2VjcDI1NmsxoQN7HHah7WnGluJb0mqKelBJ8rpvKlBb9t7848BwJ4qau4RzbmFwwIN0Y3CCIB-DdWRwgiAf", + "CVK4DBIVPD2JPJOPQJQX77ZAWU.all.holesky.ethdisco.net" : "enr:-KO4QH3RGFoO4KTuq8gxJC_mjnPBqNaDvoX_xsuYbDOqvJA4CPJs6nifZuqE2sTB3O-kHvEI0dbFJ87FoWkKhgmXYuOGAYrbEhVBg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEV7HyJc2VjcDI1NmsxoQM1AMXary_Hiw-yesOw24q7GwMH_DmNHHitFPU46foJ24RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "O4SSPZG3DFA7PHZ5L42DVOPWDM.all.holesky.ethdisco.net" : "enr:-KO4QMrNy68H-dYJbPM7snJ1UrV-TPXA012sdna6WktkfksMED9fE3g-kzp7gBgOFEdFi1KIiEv5MXXAoOt1KOMVc1eGAYw66STEg2V0aMfGhJsZKtCAgmlkgnY0gmlwhF_ZxOCJc2VjcDI1NmsxoQJUhK-aKtl-Gc3ZnfF2k7oLFXmpGCHEi1_fBxAEfc1P54RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "TGKURK5IPVCPXS4QPS2KZJCP3Q.all.holesky.ethdisco.net" : "enrtree-branch:E4GGBOZ5L2UFPAKJIPLNBA2MKA,OF3GRGFZUAJTH3T2R2EXSYAZOU,D4GT5QUSIVDVPURCUOLE4WITZI,O6EQKGUJOO2BQGMADYZHOSZXLA,WRRS22MCKTZ4YS3ZLPUJEUAHUI,3A6T36TYZJBFLOHMJOJGL5SPXA,XPTWTOASNO4WEBXH74WCQ5EYTQ,2XMYGJOCRIHHMHAFVRH3OES5QY,FYJOHHN6LBARRNNLI6SK42", + "UIGSNQMHERWJIZCP2OLUXSZ3KM.all.holesky.ethdisco.net" : "enr:-KO4QEvn7KsE7UDJ5F3HwtSmGNSy0JSYDLfp9uJRtq5WfSsuOdkfq3V7GVSDo7IfCTXYAtQfRwT9q88II0sajv-m_CKGAYulV6JLg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMwQ9ISJc2VjcDI1NmsxoQNVsiSzpuxjNDgNToi4u48-5kCgvsjuoUsvlG9VrnEjhoRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "3T4CNEKYCJP2PJ3EQVJSFKHH44.all.holesky.ethdisco.net" : "enr:-KO4QKNzTVC-wa7whLxFFss7NzEFDdVuAjgEebNGGK9xTWKjHTjqmMNfY4_h90iqlSYM_LoGyH2FFXhbHT05zKyALMSGAYuqLQ8Sg2V0aMfGhJsZKtCAgmlkgnY0gmlwhA360gaJc2VjcDI1NmsxoQPSUi99W8lkrquAhLsmslwXU4-dxxVfied-w_yqYEXpU4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "5ZWIGTGKMFAFF4LAODABANSBOQ.all.holesky.ethdisco.net" : "enr:-KO4QFP5g9U1YtBLeJi56xoBll8E4eUJeAAYMoEg1l0xPXRybh0OYNZfteBjkHMwbucccNdGB0amzNR4XtxmAfjoFWKGAYrbEjd8g2V0aMfGhJsZKtCAgmlkgnY0gmlwhF_YY_eJc2VjcDI1NmsxoQJb-JLk8MFQQPMz3QI2ya4FtZdHqZ1Nm0xD4MtOYc9ifYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "7J7D5R4SH6LVDCFZ5ELOP46IP4.all.holesky.ethdisco.net" : "enr:-KO4QArnrdy9hfW8KHAwokQf-x0LbdoDQ4WoK0H2dXqhP4D-TflLF_Ywf2lh4ybXao8jjNSxC53MzmFPUI9A8k06XDqGAY2KrTRtg2V0aMfGhJsZKtCAgmlkgnY0gmlwhLWkyzGJc2VjcDI1NmsxoQKSAHhJOTtkBlasBECB1mrukuL6jqOEtxWkpH-PfDp8h4RzbmFwwIN0Y3CCequDdWRwgnqr", + "5NHWEAJHKSAO5DIIYAZ7MQJVSE.all.holesky.ethdisco.net" : "enr:-KO4QMiOfa0JolrO470F-mRKslpq85BBGRz_JjoAOOnLuUdxBObP7579igrB-YGSdaUtb3Ih9QXBHRAl0PWYmE5NW32GAYtIvzSsg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMF6nk2Jc2VjcDI1NmsxoQI57ZbbGPYfl0DtrsmjeKDosRG14eV2qLI8AE6FsP2oy4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "OGKXEQEVGNJ574WCU7KFNTXNQI.all.holesky.ethdisco.net" : "enr:-KO4QH_WNLhz4qmL-EOMCQFfhE3QOOvyG5uyX0RR77gYrsdcJnUxS5OBQWyMqhRQLO2P9htXeBxJ4kR9ez13qQRiBp6GAY3m5RB8g2V0aMfGhJsZKtCAgmlkgnY0gmlwhIj0RveJc2VjcDI1NmsxoQJFRRKo2aP4qXB8sZnA6_rJqKgo8FZGT2i3ipEzwXCayYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "D4GT5QUSIVDVPURCUOLE4WITZI.all.holesky.ethdisco.net" : "enr:-J24QAgjCyFyNsDJHJwpo6uy0l7yqJD5r6WF58M1T7_PqsyKC0t6y1iTsQX4Crz6iv5Ijc8LqPZIC6vhA_F5GqkdkgSGAYtfbIIbg2V0aMfGhJsZKtCAgmlkgnY0gmlwhCvIu-OJc2VjcDI1NmsxoQKq0Xs_18RfgehL1rawMx2B3VMaH9d-YNCY5kmHuaCnqYN0Y3CCdl-DdWRwgnZf", + "OOQAXEUVKR5EJ722HM2UANT3R4.all.holesky.ethdisco.net" : "enr:-KO4QBU5Jlmco2sEPOXNEctSfBJfxdgANrWXVLS-BVDQYrU3N_oaeceDQIH82zZ6BSi_F-FQpSKhEVYtPDxI1BA4OW-GAY5bg91Qg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIrJH32Jc2VjcDI1NmsxoQMXPocPKx0wRocEEzASwUfzDLKd1f-Jifv2pyIu2M8vaIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "ZJOONJUDYAQ53XOJU4KG5W4ATM.all.holesky.ethdisco.net" : "enr:-KO4QBGQGqvIoSv3C7d-bCiMst5GGEvIQaQ5pAHs5On0MeNfcVjcZgd72l2UUoqf13HoKHUnVG37cmHw-11H4Y59F6iGAYrbEhVng2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFtO36Jc2VjcDI1NmsxoQJ3QRuoMLUn6djLfYCFTKSV8kUWRQTOethOOhLDW6ezdYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "INA5QYTPU6WIOBT7IC7U3WGDIY.all.holesky.ethdisco.net" : "enr:-KO4QDpTzc6voIJiaQO1T8cbJvPej2OvifSuuDkQVrAcC3imJXT9J9zt5DelQaMAeu_uk11kFFgYJHBAE88J2cvxYGqGAY1olkLcg2V0aMfGhJsZKtCAgmlkgnY0gmlwhLnRsD6Jc2VjcDI1NmsxoQKfdroTKrg5QqtuIyfF7LivDR4GjeBnA8xQrAg5ma4pzYRzbmFwwIN0Y3CCy_KDdWRwgsvy", + "HTMBFBAIZBACDG3EZZ4JVANCAY.all.holesky.ethdisco.net" : "enr:-KO4QHHstlNCTI8xMnZ5mcIRvd8lfo9YoVeyv4pqvhtkxGI5PhYqDDqu1W5XqHWCMcTt8fpVgu9KojZHcyi7fhQhPdyGAY2_zUcvg2V0aMfGhJsZKtCAgmlkgnY0gmlwhL5czy-Jc2VjcDI1NmsxoQJpsTl5abUECWCfSno785tWBNPiVCJ7GG4eQTf2Cf1qBIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "KEC4722NVHE3KX3IYNZC34C7NY.all.holesky.ethdisco.net" : "enr:-KO4QMjtE87kVQpcuRPXV31w7fsvyit4Fw995wyZ_h6uK8atWGOhXpsi3xXsBQLgJFBWmNHgTw-V4JvZNFZkFHLU_8mGAY5hRoXMg2V0aMfGhJsZKtCAgmlkgnY0gmlwhC1Mp4CJc2VjcDI1NmsxoQPETNdXN5Cshfr8-rIIMev6E_MGqD-jzqcXW2yfzT3C54RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "LNO6EN3Y2LJA5YOJMEMODAJFWM.all.holesky.ethdisco.net" : "enr:-KO4QMIX_--Ar7-XnFcUVlJpBBUfZdfNOftELgvHhCGvbLGTMT_Yqs2M15BCG0qoHsYkVBP5b4wd2wqNyNIfFaTtQ7qGAYrXxOTEg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKEeJc2VjcDI1NmsxoQKG05NXftvaats582r82_zpOi2C54WjSeYwxKfSNeSJaoRzbmFwwIN0Y3CCdmqDdWRwgnZq", + "R6EQA5KQEQM77JJXB4BHHTDF6Y.all.holesky.ethdisco.net" : "enrtree-branch:5AOSND63QKPVT6EWNMALKAFC4I,WAU46WR54TX2VBK4QM7NFTA73Y,BBNHIHYBPXVQTDZQ4JXK4QYDMM,OA4ZFFGZNFUC3LIYCUDJOPVR6U,KMY5HFBGCUDWN2ZXE6QNUQHGYY,OGKXEQEVGNJ574WCU7KFNTXNQI,CDXRYZFKH6OUWTA2IJ2MYIMYPM,IPU725BUGL4HIOTSOIH3KZG5ZA,G4MENE2MCGXFPYEBF7SQEU", + "45V6KFI4NL43JFTJHL77KP6MQA.all.holesky.ethdisco.net" : "enr:-Je4QEedukMKHefwNNtNam8wvx8_0GMFHLC9nA-TWWPMTZ9FD4C1CrjqFxLNruZPGL4T1ApKPtes_ApKbwwOEc4dNxIDg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIrJ_vOJc2VjcDI1NmsxoQIKnge3StO67CequDTr_QwD_V_qhS7TnGO-tgE472AFroN0Y3CCdl-DdWRwgnZf", + "5RYAVZN3SSZZQ2KISUO7XDBPE4.all.holesky.ethdisco.net" : "enr:-KO4QGMFa1U36mwISUTSlJN8q2EKJSw4WHa6PAMrHkhL9-naVdAWmGuwfpel1E5_NBSnPWRsG8FzmmUH61iq8fodXcKGAYrbEkT3g2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFtb_iJc2VjcDI1NmsxoQJ62Txw9OKK7DTE_CZRC6tX_223dks-FcinnSL1uXUbTYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "KMY5HFBGCUDWN2ZXE6QNUQHGYY.all.holesky.ethdisco.net" : "enr:-KO4QLBXgetH_VHZgzalnkfF-iibF4km3OCLzY1TK27U-yG0ZA7Z4C8DL-da5x65khpgvOBa8c2CXffZBdTtD2BZQCqGAYrg47Ahg2V0aMfGhJsZKtCAgmlkgnY0gmlwhJT7RHyJc2VjcDI1NmsxoQKeRdcysv01DZ_Mzk9t1jcWW-YTjP1fPIrHVpw9hc_Q_oRzbmFwwIN0Y3CCdn-DdWRwgnZ_", + "YUZZDZD57PTNEIB5QL3FEXK2ZA.all.holesky.ethdisco.net" : "enr:-KO4QKuFVKbKbtK1djMy6q3TLuAWYDAjoC20cYPdhcOk7PhUdA67TXr9vTGfZZ9AO7ivmQZyRW4w9TFk-29_xP72bkKGAY2EAMvIg2V0aMfGhJsZKtCAgmlkgnY0gmlwhF_ZKGKJc2VjcDI1NmsxoQN4FBojS_W4gviNwaTDsIXiBEGXaQMtUzxQHwKWDmMR74RzbmFwwIN0Y3CCequDdWRwgnqr", + "2XMYGJOCRIHHMHAFVRH3OES5QY.all.holesky.ethdisco.net" : "enr:-KO4QAMrUvclx-EHZrO6x4K6fWTwaKh2zS5oWJzVU6oZuzZ2GG5xdQky8tIrpUUSKGB6XGvr8TvmiS7Dimvxx9Hb1OOGAYy06xQvg2V0aMfGhP1PAWuAgmlkgnY0gmlwhEFsSLGJc2VjcDI1NmsxoQJiz0q4ywyhHvmXBrWbFykzflq_J6cmLFIbepO9PLBwhoRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "5NQ4MKTYBPBMTGUT5IKUAXEGBE.all.holesky.ethdisco.net" : "enr:-Ke4QLrsveUYt2tacm5EZETFc1F3EyvNYfRRkRhljyeLMIzccRlPI1kKmBWuELQs5iAIRZgv92P3Fxx_zJ3xyUbN3muGAY0x7p4Ng2V0aMvKhMYaYJiEZRbqwIJpZIJ2NIJpcIRU94PXiXNlY3AyNTZrMaEDBxF5W6guB9qZoR-c_zuDUE1UHyaH0FMjJKHC9Jq4ji6Ec25hcMCDdGNwgnZfg3VkcIJ2Xw", + "6WAXAZL7FYKEJBZOPWIHCI4F6E.all.holesky.ethdisco.net" : "enr:-KO4QCagHy1Q7UED3AJfgVHxFKOn_DRS8UTUg0okE7fW8VTcDCW7wlzWufdWCtdUwHpnaf3EZpRn2YRuo9u4LS-Oh3qGAYrayS-wg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKI2Jc2VjcDI1NmsxoQJYC74L06jkGeWkmh1aSjeBYVvzDCWEvtbd111Vu8WiG4RzbmFwwIN0Y3CCdmqDdWRwgnZq", + "ZRVWX74UFHUWOPCC7LOPLYGIZA.all.holesky.ethdisco.net" : "enr:-KO4QBfm8BEr7OIRv6UT1t5mhE2szefodQuIIBKMFnyvbky_K-wff-l6pqLwfLyUblqFNxqM6Xtcnari4ItzO9236DuGAY08Swa9g2V0aMfGhJsZKtCAgmlkgnY0gmlwhC7r5ZiJc2VjcDI1NmsxoQIH5RAmLOvYi50mYcMnglYJlz-EC0E2YsetPVUzvbs5BYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "2WHIXLN5QX3JEK3SSH7MXKTJMU.all.holesky.ethdisco.net" : "enr:-KO4QF2wWLadNPZ_qwOIZkLxPEJjj3N23892JKdtSOeeC-X-V0bZsjQ3QqpidJviGT0WrBakFANcbSa9IqFSOslJLV6GAYrayMEMg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKIGJc2VjcDI1NmsxoQKQ_STV3aCaM5we4KxMPMpdG63xPZieKw8A36OR2tuouIRzbmFwwIN0Y3CCdmqDdWRwgnZq", + "I2Q5U7BDYSM7O3O2IKIRL3KTZY.all.holesky.ethdisco.net" : "enr:-KO4QIvQh_txoF6gwXTRsMzzvHPdwQrkCHQYt4egwTWqUNQiMQh1yJ71U8UXs8b9pP9R1mUDTaiQ7-e0eB2Zbvouyb-GAYvSz8f0g2V0aMfGhP1PAWuAgmlkgnY0gmlwhJVmk6SJc2VjcDI1NmsxoQIuT88b-jLJBI40hV4VY3YeJnmYT65v2QPjyzuNG5dyF4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "NQHD4WNRRGE5JHHX6IDKGIF7KU.all.holesky.ethdisco.net" : "enr:-KO4QMTq7zJT6fLS-LY7n3hWmcwJqxRJraGJcM60U9Cej2lRRE-u_KhOd9r712IBEOI39uiVQEXGdBZvc-dJIESUcOOGAY2dUsseg2V0aMfGhJsZKtCAgmlkgnY0gmlwhFD5eBSJc2VjcDI1NmsxoQO-UyE5ad7DaM-f7fZi4QEwjfxIR6HS-l-lTSlv4ZLdB4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "PX6K4ZETH5UX56IKHKL5TNOOMU.all.holesky.ethdisco.net" : "enr:-KO4QJqslvj0RYuX2CI61yCK7VuyY9Ik3c5EHtpkKehriXyqchhCDMx4sRuewcCAiWO_frsUevV9GXMPnsO2nzCrgFGGAY25poLHg2V0aMfGhJsZKtCAgmlkgnY0gmlwhKfrsZeJc2VjcDI1NmsxoQOe9l9K5UW0RAuQTH1q6CwG_UzbJyKLGSt4lUt5_tVmyIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "CK3HQLGRQATDVF5CEGHX4DVQRE.all.holesky.ethdisco.net" : "enr:-KO4QJy9FHDJZfMqgGGIVGn-j0rHI6JDjTWEo7sN1mBkzO68ZId8jkRQfgZ5dJkv4GGm2rsvrWk6OJYF0td7n0Ve9GqGAYu0QPj0g2V0aMfGhJsZKtCAgmlkgnY0gmlwhHTK4wuJc2VjcDI1NmsxoQIYyd-9-wdwfPPu6uRcnTKkQaCekdhR7488-2pxl36AZ4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "IDXHFSATDDENJYOYJC2TUSUYOY.all.holesky.ethdisco.net" : "enr:-KO4QNWpVQT5pw-oUuoa7JDOVs1KheGaPRAR_fIarAflnc1yHawwEpptaB5JWkRXgh-wJtk2KlHE5zGICXefHD23Z4qGAY2-EYHHg2V0aMfGhJsZKtCAgmlkgnY0gmlwhK35GlyJc2VjcDI1NmsxoQPV0hDwmz1HaZ4pLm1hzee38NMugxA_7j3v6hbxP_LRCIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "UYESJQUB2BHP2MSJNUSDRGXQ34.all.holesky.ethdisco.net" : "enr:-KO4QB9gKzGbKvqoPJZymq3AiwZvWSHOup-36D8ec3CwNZDFRoEmp64UmWaP0St3OXDIV4Q2Rm0MnelRnAjvn3HRfyyGAY5EPB9Jg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIrJ3_qJc2VjcDI1NmsxoQKHt6N7ehDnwT8_lFh-C4KgqUplYwDvYy6rOP6DFRO8lYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "76OAILAEE52CRVMYFBIBMSEUXE.all.holesky.ethdisco.net" : "enr:-KO4QDRyM88zIGJ2P0Ya1W8fucbp8kRTbVzV8k_2eawB0W3ZHMQ3o0HlbXVw3j1rMgN167LmsaMt1iO015HMVKGT2GqGAY0rjdqhg2V0aMfGhP1PAWuAgmlkgnY0gmlwhKFhcJuJc2VjcDI1NmsxoQJ2emahe6-2fq_hQqxm99rgYi4TSzQ1ky4utO3Tcpe-yYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "NLYAF5XVTTQUOMK4LYUF3FXNPE.all.holesky.ethdisco.net" : "enr:-KO4QMiia-4vyLiHQQhxlbmCKdOTRqOiZin-BdAdi18LxNqqHEiuIQQ-F1dZu8sKi63vEk9zwr5gfMnxXQZCThEaiDSGAY2UmHG4g2V0aMfGhJsZKtCAgmlkgnY0gmlwhMb0_ESJc2VjcDI1NmsxoQLb1ZwjPQw7AjCvlHQpt9bmePVD85rbHbnFZ0naOB1RNYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "QXRV67JYJNOYJDPC5CTKUVG76M.all.holesky.ethdisco.net" : "enr:-KO4QN1MRyJdifSbU3Mg_GUI0_ApfQQ_BcPgDV0FXjVvk4iEWX6EgMjXkI62H1S0DFnyWeHnvr-caabqiKtlvbIBICyGAY5Cyclbg2V0aMfGhJsZKtCAgmlkgnY0gmlwhK9jhAaJc2VjcDI1NmsxoQL8tsMGtv3EuJfUdI8AzGBxYynVj2HlNoz4wi3COC4TTYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "4LPHENNNENCUT7P3MWTB5IFRTY.all.holesky.ethdisco.net" : "enr:-KO4QF8nbKqJkOeYlF-akOVBYzusVwSjI_ra_t7TmM52mEisQLCwNAjujSSc9-6ECErhss-gVTgBDzhY5QmVcF0PuZOGAY3Li2SUg2V0aMfGhJsZKtCAgmlkgnY0gmlwhCL__BOJc2VjcDI1NmsxoQLHimHBtxJxch5bc20e_O8OdeN3UQx6TQQWqaYfyi66OYRzbmFwwIN0Y3CCdW-DdWRwgnVv", + "5FNWKKKVUPIHRKWZY5P33A7E74.all.holesky.ethdisco.net" : "enr:-KO4QMD5jLnGVTSO75tQPB1JOCFKmtSPRi5pmE7wMwjDRfVKdwpZJvr8qfv1WmfRnJFzH5qV2sfC187nT9mp-xE_-gGGAYupHwgxg2V0aMfGhJsZKtCAgmlkgnY0gmlwhE6KPgeJc2VjcDI1NmsxoQP9YVLocwkml9Z9YNDRpmzVceRY-Ts45qiPOBC1OsxuUIRzbmFwwIN0Y3CCdmmDdWRwgnZp", + "HIDVATDVB36L2MASAWA7SBJAII.all.holesky.ethdisco.net" : "enrtree-branch:N7HAL5M6HNZBGTWM3LWFDRX4WU,PX6K4ZETH5UX56IKHKL5TNOOMU,I2Q5U7BDYSM7O3O2IKIRL3KTZY,O4SSPZG3DFA7PHZ5L42DVOPWDM,Z5DOK3IB5X3IBOVVIK5MQCDYNA,ZJOONJUDYAQ53XOJU4KG5W4ATM,NLYAF5XVTTQUOMK4LYUF3FXNPE,XPX2T64BHDYLVGT5UC4GVIQIVY,AHB67R6KGAKWRSBL7CRAO3", + "FTTHOFPRPIHMJNZA3VZUAW5TIM.all.holesky.ethdisco.net" : "enrtree-branch:V52J3WKNVGG56JFRKYDLSZREJY,ZCAW4EMBOL5EH4MSMW7FY6JMEE,2SA643JTHJ75ZVPFHAKLXI2WXQ,5RYAVZN3SSZZQ2KISUO7XDBPE4,366Y3UIIKK2G5CUS7CHNPZMPEM,7J7D5R4SH6LVDCFZ5ELOP46IP4,7QEPEVJFOCL5A63GZLZ4IZNMOI,OOWRWZXNDJVJUCDDOJFOY4YB2Y,H6XMM4W6QZEQDAPVXGHJSU", + "O6EQKGUJOO2BQGMADYZHOSZXLA.all.holesky.ethdisco.net" : "enr:-KO4QAg_or5YgVU8ScSgcgvNmLMISW0LA4L5GtRLxmyVUlGRR4GNuEZ9q_tKtZdAbLH5B-FN2ie8hp0U6P90d39xtyWGAY3d9Tplg2V0aMfGhJsZKtCAgmlkgnY0gmlwhAMRsEKJc2VjcDI1NmsxoQJ7STjsgZvt1OOj5krr2l2iAwlY5AGl-dgTziPZsCd9qIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "XIOWDM2BTMANILG2M4LCMO7W3M.all.holesky.ethdisco.net" : "enr:-Je4QCvStdCVA-jcYsFoXmDXjXlp-Cd_9sSMdi9Y1LM3tGH2C3k49yrWy-Y2Jg93ikASYljvD9qVUES0T9q5htcbL60ag2V0aMfGhJsZKtCAgmlkgnY0gmlwhLkIa-2Jc2VjcDI1NmsxoQKcjBIYl8vFVYVBs5trEl-Zn3IE7u1ZiiV986p0QCrAL4N0Y3CCdl2DdWRwgnZd", + "OL6KYVYI7SGS7U34FW3RFBA76A.all.holesky.ethdisco.net" : "enr:-KO4QIlf7XLihA1hw9d44SB0ENJ40RT1RmF2KrT9a2kRZfZoWPdO1jRi3GNnNSCLDxLo-aFc6uaGi3zxhCD8lg1aZgSGAYuoht2-g2V0aMfGhJsZKtCAgmlkgnY0gmlwhDNR0MWJc2VjcDI1NmsxoQKFvOHK4c-F1tHwdgsUa2yru5RrWyiVP9kRLnrw1kXcd4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "NXU6DU7HEM2L4DN6G2VHHFT72I.all.holesky.ethdisco.net" : "enr:-KO4QLbrxgLUdY0NhpBPVfilpwn4d3gPFIEqoq62P1piOkVmGwh5qpbKtor5FfE4FM_eezlzMdeCM7bzau8ciKZU1GiGAY0-AhsZg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEp2iPuJc2VjcDI1NmsxoQOGZRfYj6RgBYWulaw90MVPDt_9F-oM3PzevNe-RS6KyoRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "RUQLNXP5XBWPAOEKRUEBAQXXXU.all.holesky.ethdisco.net" : "enr:-KO4QPpoyIf1DfiiOW8Tt34tBuA68Qd55cyWcqGOCFJtcHKtcqYr373NJPyQVd8ktsuqZU8L_ERSRAfKesEwdBARg3OGAY45u3jEg2V0aMfGhJsZKtCAgmlkgnY0gmlwhJRxo0WJc2VjcDI1NmsxoQKhMTQ31q7EAIHkisiA02KVyFZZLq2Q9w4_jgmiYCsF_oRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "RUJF27NEYDBNQAFMI6K6SKDVRY.all.holesky.ethdisco.net" : "enrtree-branch:OOQAXEUVKR5EJ722HM2UANT3R4,HOXANHAMPVCFA2QR54VEMWCWPE,INA5QYTPU6WIOBT7IC7U3WGDIY,3PMAED3BC2HUS7AMEPGJDKENQY,VJJPHI6PWJ7NFBTW2XJH3JMMNY,5ZWIGTGKMFAFF4LAODABANSBOQ,UYESJQUB2BHP2MSJNUSDRGXQ34,CK3HQLGRQATDVF5CEGHX4DVQRE,AYB76YY6WPJVIBLRB5U7L5", + "ZM7CMNKKBVIXY7WGMB7GHYUXMA.all.holesky.ethdisco.net" : "enr:-KO4QNhl-I-sZux_gBOVLLbMdw8kMK9fSEBBNYukE2r95hDLJ9rOuhZgJO2dvNU_vCcuiqJNJHj8N3olsc7srxryuuCGAY0cB7Qsg2V0aMfGhP1PAWuAgmlkgnY0gmlwhJ7caOWJc2VjcDI1NmsxoQIyp37cpS7gvrKk0f3VxW4D9Jx7mOvSAQ6vOaQppsYp2oRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "FKSM7XWAT4KW3TDLRHWSXZPMKQ.all.holesky.ethdisco.net" : "enr:-Ke4QNDn6wmnaZU1IYGb9lX8zb2QFUpa_yen8vFTnuXpW3_iah53AKPZX1D05HE23bw-UtR6zXOpCquB9XDdxvGhZrGGAY4oYEAkg2V0aMvKhMYaYJiEZRbqwIJpZIJ2NIJpcIQlPPlxiXNlY3AyNTZrMaEDiVRaEbnUMSPCtpxluUoBgOy3Loaa6SaVW9QK8BGTFJiEc25hcMCDdGNwgnZ9g3VkcIJ2fQ", + "QXKEJG4XZEQSXNUY7JJYCATPEI.all.holesky.ethdisco.net" : "enrtree-branch:FEORTRPBZJSNRB3XCLK7KCMACQ,2M2GRZYAFKAVHHU5HTLUHPYHCM,KEC4722NVHE3KX3IYNZC34C7NY,NZCRGKUE7ZXSIS4MIQ4WUS76BA,3PYSNKXWWSRBWV377EFNYINHSA,C7SDD5OPASV7B2XUOXF5NLBBKU,QY2XP6FTX7QKFNP6TNZ2MOHZKQ,76OAILAEE52CRVMYFBIBMSEUXE,2QHFEUHLG6Q35B5LUS5BF3", + "OSHAABXYJSRWW35VOMYHZUKJXU.all.holesky.ethdisco.net" : "enrtree-branch:LHFO3FXNRHB5B3YSTKUALGMEAI,5FNWKKKVUPIHRKWZY5P33A7E74,NHLNHXPRKOALUKH4MZ7CIVDONU,BAY6SKB2RTQCM7RTSHAFQ6TTBQ,ERETBCE2BAPJRSWIEUQV3QPP7M,MFPFKTJREYMESD7STJJOFYDVV4,MOBGCDYGQA4CNBQZDPN52JC5E4,RUQLNXP5XBWPAOEKRUEBAQXXXU,NJCWGLUAGBNZQYPU752O5L", + "WAU46WR54TX2VBK4QM7NFTA73Y.all.holesky.ethdisco.net" : "enr:-KO4QE70KmVYr_jd9JOMBFTIJomf3oliyhugCm4kFnzYD7dcJk3y7dVYyakJdlWHBuk1t4hDjW05BzeVf2f-V0LQ-r6GAY0-Aab1g2V0aMfGhJsZKtCAgmlkgnY0gmlwhCUbP0KJc2VjcDI1NmsxoQMQLfyyzC-YY6A53fEu5a8tP-JWCUM3vkX-c7nRyu28d4RzbmFwwIN0Y3CCequDdWRwgnqr", + "CZ7ESLY5ZH4ULXKQ54XIFDCOUI.all.holesky.ethdisco.net" : "enr:-KO4QOQjO7c9J74CpllRngkrEXHLw-W8VqtJXxkLWPRIXC1CFL4TCjEM4ZfRW6MFlqXERaY3h9mztZXLAo1lf1DAS1qGAY2D0c0_g2V0aMfGhJsZKtCAgmlkgnY0gmlwhC06cBWJc2VjcDI1NmsxoQN8oyz6RCujPq6Hj9N8dBn3EeJ4A69HYeYnyLRiXLZ1UIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "5HSHHSS4QV3FKCNDETO7CEXMZM.all.holesky.ethdisco.net" : "enr:-KO4QAOJNWWAbN0bM51WFaFZimloFw1eBc7Pi3x7uMejmIToDZxBoAVtvdwTg3p6T0Mwmvuot6uT7PG2OjGxkk3P-iGGAY5cIO3Hg2V0aMfGhJsZKtCAgmlkgnY0gmlwhLAJA5uJc2VjcDI1NmsxoQPdRE5vPW9Wj-HRwzubnKAA-1n0p6CAOGLP-E-GzE9EnIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "DAVKEW6RQ5YTYHKCJ2A2J5GVCI.all.holesky.ethdisco.net" : "enrtree-branch:6WAXAZL7FYKEJBZOPWIHCI4F6E,OL6KYVYI7SGS7U34FW3RFBA76A,FKSM7XWAT4KW3TDLRHWSXZPMKQ,5HSHHSS4QV3FKCNDETO7CEXMZM,CZ7ESLY5ZH4ULXKQ54XIFDCOUI,ZRVWX74UFHUWOPCC7LOPLYGIZA,FFM67SMD4N3SK4ZQTY2HOMU4F4,IPSUCB6CICZIW6SERKZL7FFTIM,7XE2DGFKCGOST6BJ46JPNK", + "ELYMU6HPEMGLIYO2UO4YNUI7CQ.all.holesky.ethdisco.net" : "enr:-KO4QOWLzzuDHlwrEZhdOmeHEuFi87mX6iwJ_V820RsBL0mCLngiBaLbhumS1Q8zIc6YPzCYN07nigSMr-OXf19z3WSGAYra1TQYg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKFGJc2VjcDI1NmsxoQO4QASxyNUTDVIRkvEDQejetqJ4roUtGDD6U8CoI9wm-YRzbmFwwIN0Y3CCdnSDdWRwgnZ0", + "VNVQZHCW3CS7ELWR3ANLO7RFBA.all.holesky.ethdisco.net" : "enr:-Ji4QBY7W0c9-rsM9r3yeBngIOe0LFJx8wew3ZatidckbRSzAbXbvc5ahmadpUcrPG2oDm9ziwxw7maQtzEs4J2AnWeBr4NldGjHxoSbGSrQgIJpZIJ2NIJpcISU--s8iXNlY3AyNTZrMaECwwCgmDrePUxVSsOZqgS0USzkXwzkqF0-QK7a59EsnrCDdGNwgnX7g3VkcIJ1-w", + "MFPFKTJREYMESD7STJJOFYDVV4.all.holesky.ethdisco.net" : "enr:-KO4QJfXFSQOpwMtEgz0BSB0gJ0zXQZ6yMzoBfXYCK3ATxulM_mkkQhMWUHsrNyNH9cQYfxRbN_hEGfP3id3IfLiv7KGAY5iEk-hg2V0aMfGhJsZKtCAgmlkgnY0gmlwhGQmekqJc2VjcDI1NmsxoQOoFPqpQmRhcIfVINZ0knq8hxe2tUYnTSLEC-6Xoly7jIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "HGROZTZI7YPDW5F37QVUPQEBG4.all.holesky.ethdisco.net" : "enr:-KO4QI3-xQxY2u-misXaxFtpfezZr0Fk0jiL6U7ZAbwV4yCRcWxgNVOxLifoUOm9uMUqH4wlGxIYL7onmzlavqRIjDGGAYs5av4dg2V0aMfGhJsZKtCAgmlkgnY0gmlwhJRxqGeJc2VjcDI1NmsxoQPc4dhahamqQVxv9D89fNMO9DOP_YaPJRQ3EmT2XantU4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "VJJPHI6PWJ7NFBTW2XJH3JMMNY.all.holesky.ethdisco.net" : "enr:-KO4QHCu9ZPe68fKUXeUzje0Jwv0D8UCrAQ_K5W9KHrIpTdUYWIGwbzyl8heZ1cxijlyUhN1WB0HZpEBThUmrdm-uLiGAYra3Fk3g2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKJOJc2VjcDI1NmsxoQP0AAgBs1kDG4XBe55HY0J4F7GjHiwQVUY-xFKYXOB594RzbmFwwIN0Y3CCdnSDdWRwgnZ0", + "2M2GRZYAFKAVHHU5HTLUHPYHCM.all.holesky.ethdisco.net" : "enr:-KO4QIPWnAyt-tyaS1cnMwEJaFdvDd-MpwEcVv26cfaZ3Ffkc4WCpHqzPmd_SifEsxQf7ontOz9a1EPjbxlsu_V1XZqGAY1UHtm_g2V0aMfGhJsZKtCAgmlkgnY0gmlwhCU85MWJc2VjcDI1NmsxoQNyzc5NiamRt8y5TQ9Qj-EZAYeCb7ZaclF5sOdPrErpbIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "OOWRWZXNDJVJUCDDOJFOY4YB2Y.all.holesky.ethdisco.net" : "enr:-KO4QBKCkXrhG-hN-IY-O03b5A4JHaweb5xuuSZP4OdJ4uSSZ7E1-yioWl_0WSF8ShOfNU_7wgSCIwBPg0d4RD4w4sOGAY4oBl8og2V0aMfGhJsZKtCAgmlkgnY0gmlwhFJB7YiJc2VjcDI1NmsxoQL-RfOTNeu0SmlCfWAVQaQBUXF0WDEUCyGXv1LWC2_xqoRzbmFwwIN0Y3CCequDdWRwgnqr", + "E4GGBOZ5L2UFPAKJIPLNBA2MKA.all.holesky.ethdisco.net" : "enr:-Ke4QD_Gz-YGWvzpAe-a1l2KHz2pxG_nPUYTcHQDN1Wuvkk7TrEDq-HKIXtA_mbPukz9qLLW5sfkJH5mYda9eDssvkSGAY1Mq2nIg2V0aMvKhMYaYJiEZRbqwIJpZIJ2NIJpcIQf3GvbiXNlY3AyNTZrMaECyosSeovqxsDXTua8c47z7EK--WogcD_FCwDX6tHvVQSEc25hcMCDdGNwgnZfg3VkcIJ2Xw", + "KYTZHQX2PSOGY6RQTCVTUKTS7E.all.holesky.ethdisco.net" : "enr:-KO4QJn2ahKcTeE2karHSHOiJgE9b2BJWo54NS3nzjjCDnsvZ1JjDl8yulvzn8qQA48zCtHkiVMEVNHtdf_LM_N0Cs6GAYxElMbzg2V0aMfGhJsZKtCAgmlkgnY0gmlwhJ3m3GOJc2VjcDI1NmsxoQJzs6KencrR0_ylBVNGuZaknO_zQUYOrq4rjj_FmTlHN4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "O25DXMPE4UEZ6SFMQRBGL2E2I4.all.holesky.ethdisco.net" : "enr:-KO4QLC4qJt9OIKLkxROeQMNcj4ZuH_QZsVcS1v-NhzBGAf8B-5xF3GYqtMp40n6nwDg8o1yUs7xEx0kgf6wBJF-AuqGAYrbCvwJg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKGuJc2VjcDI1NmsxoQPogWsyNFpt9VYFYrsdUS4dxK2BMeL4FY41ZzgBjeEGRoRzbmFwwIN0Y3CCdmCDdWRwgnZg", + "RWRV55FT3DKDVGZK7AEU2DR77Y.all.holesky.ethdisco.net" : "enrtree-branch:ZV5CYUMJPF4XSTFHOG65622KEE,QDCV4SQTQAGHHA3DRA2TR4LNMY,MZGQKIQKIOFO3W2GJURJUXVYMQ,V5LTO36DCXU2JCCSSJ76E35OFU,NQHD4WNRRGE5JHHX6IDKGIF7KU,ELYMU6HPEMGLIYO2UO4YNUI7CQ,VN7EWW4RMUZM6KUOQ43GJ33DWM,J7ZIDKH7CKVIYY244QVU6RMPDM,QSK67XVHDPEQH7Q54HACQS", + "TMY2W2YBNCXUUNA3Y7QXWVQLRE.all.holesky.ethdisco.net" : "enr:-Ke4QEsOrFCJqnbVXiuSh3DKjvS3RlUoOrrLRlKp2mAqmCuJQBVLFREqABNWRvZfLOxDPEtvcRzDtDW2juSa1vd2xj6GAY025-JNg2V0aMvKhP1PAWuEZcNqwIJpZIJ2NIJpcIRU961viXNlY3AyNTZrMaECoqR94LV1BQMpEDbmfJdd8adEPWHV7qP09nePPVm9nLqEc25hcMCDdGNwgnZfg3VkcIJ2Xw", + "XTJ3PFTPBB3ATDDWAA6W7RRKMY.all.holesky.ethdisco.net" : "enrtree-branch:5AHKPN5NN5IHEH365X6OYZDHDE,AUQGHXRAP7J3AGI6TUOQVYTYLY,IDXHFSATDDENJYOYJC2TUSUYOY,GZVG4EONSAY6M7SMLJXDCFUF5Q,LHFTDS37XFOEXQKTWJGKWRLFWM,HTMBFBAIZBACDG3EZZ4JVANCAY,ZM7CMNKKBVIXY7WGMB7GHYUXMA,VNVQZHCW3CS7ELWR3ANLO7RFBA,PA4IN34FHBJOUK2MS7YRA3", + "V4A7I7I2SFVUDFFH7AGPFXACBA.all.holesky.ethdisco.net" : "enr:-KO4QMIHR0XgkMyX4E9Xh8Fw3rFr-QWyH49lpFmrEhx1Cc_WMxm6atHPm0g-bXmzradBORfb0S9_6dyi-GnQbtlm7GOGAYr8tSZLg2V0aMfGhJsZKtCAgmlkgnY0gmlwhA_MQUKJc2VjcDI1NmsxoQMrgP3au96duBa9PQzAFPhlQ7ikkA96YWAkrItcm5zCxIRzbmFwwIN0Y3CCdl-DdWRwgnZf" +} \ No newline at end of file From f081e64e6888b47cee29bbf19e692482822c3e63 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Fri, 31 May 2024 12:41:21 +1000 Subject: [PATCH 24/26] Convert Mock Dns Server as Vertx Verticle for easier management Signed-off-by: Usman Saleem --- .../p2p/discovery/dns/DNSDaemonTest.java | 20 +- .../p2p/discovery/dns/MockDNSServer.java | 243 ------------------ .../discovery/dns/MockDnsServerVerticle.java | 205 +++++++++++++++ 3 files changed, 215 insertions(+), 253 deletions(-) delete mode 100644 ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/MockDNSServer.java create mode 100644 ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/MockDnsServerVerticle.java diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java index 695165cdfec..06cb472cc2d 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java @@ -27,9 +27,9 @@ import io.vertx.junit5.VertxExtension; import io.vertx.junit5.VertxTestContext; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -38,19 +38,19 @@ class DNSDaemonTest { private static final String holeskyEnr = "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@all.holesky.ethdisco.net"; - private static MockDNSServer mockDNSServer; + // private static MockDNSServer mockDNSServer; + private final MockDnsServerVerticle mockDnsServerVerticle = new MockDnsServerVerticle(); private DNSDaemon dnsDaemon; @BeforeAll static void setup() throws IOException { Security.addProvider(new BouncyCastleProvider()); - mockDNSServer = new MockDNSServer(); - mockDNSServer.start(); } - @AfterAll - static void tearDown() { - mockDNSServer.stop(); + @BeforeEach + @DisplayName("Deploy Mock Dns Server Verticle") + void prepare(final Vertx vertx, final VertxTestContext vertxTestContext) { + vertx.deployVerticle(mockDnsServerVerticle, vertxTestContext.succeedingThenComplete()); } @Test @@ -65,7 +65,7 @@ void testDNSDaemon(final Vertx vertx, final VertxTestContext testContext) 0, 0, 0, - "localhost:" + mockDNSServer.port()); + "localhost:" + mockDnsServerVerticle.port()); final DeploymentOptions options = new DeploymentOptions().setThreadingModel(ThreadingModel.WORKER).setWorkerPoolSize(1); @@ -106,7 +106,7 @@ void testDNSDaemonPeriodic(final Vertx vertx, final VertxTestContext testContext 0, 1, // initial delay 50, // second lookup after 50 ms (due to Mock DNS server, we are very quick). - "localhost:" + mockDNSServer.port()); + "localhost:" + mockDnsServerVerticle.port()); final DeploymentOptions options = new DeploymentOptions().setThreadingModel(ThreadingModel.WORKER).setWorkerPoolSize(1); @@ -116,6 +116,6 @@ void testDNSDaemonPeriodic(final Vertx vertx, final VertxTestContext testContext @AfterEach @DisplayName("Check that the vertx worker verticle is still there") void lastChecks(final Vertx vertx) { - assertThat(vertx.deploymentIDs()).isNotEmpty().hasSize(1); + assertThat(vertx.deploymentIDs()).isNotEmpty().hasSize(2); } } diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/MockDNSServer.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/MockDNSServer.java deleted file mode 100644 index d845c2654f6..00000000000 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/MockDNSServer.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.p2p.discovery.dns; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.net.DatagramPacket; -import java.net.DatagramSocket; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicBoolean; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.base.Splitter; -import com.google.common.io.Resources; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** A Mock DNS Server that returns fixed TXT entries. */ -public class MockDNSServer { - private static final Logger LOG = LoggerFactory.getLogger(MockDNSServer.class); - private static final int MAX_PACKET_SIZE = 512; - - private final Map txtRecords = new HashMap<>(); - private final AtomicBoolean started = new AtomicBoolean(false); - private ExecutorService executorService; - private int dnsPort; - - /** Create an instance of MockDNSServer. Add TXT records that can be served by this server. */ - public MockDNSServer() { - - try { - final Map dnsEntries = - new ObjectMapper() - .readValue( - Resources.getResource("discovery/dns/dns-records.json"), - new TypeReference<>() {}); - txtRecords.putAll(dnsEntries); - } catch (final IOException e) { - throw new UncheckedIOException(e); - } - } - - /** Stops the mock DNS server. */ - public void stop() { - if (started.compareAndSet(true, false)) { - executorService.shutdown(); - try { - if (!executorService.awaitTermination(5, java.util.concurrent.TimeUnit.SECONDS)) { - executorService.shutdownNow(); - } - } catch (InterruptedException e) { - executorService.shutdownNow(); - Thread.currentThread().interrupt(); - } - } else { - LOG.warn("Mock DNS server is not running"); - } - } - - /** Starts the mock DNS server */ - public void start() { - if (started.compareAndSet(false, true)) { - startServer(); - } else { - LOG.warn("Mock DNS server is already running"); - } - } - - /** - * Mock server local port - * - * @return server port - */ - public int port() { - return dnsPort; - } - - private void startServer() { - executorService = Executors.newSingleThreadExecutor(); - executorService.execute( - () -> { - try (DatagramSocket socket = new DatagramSocket(0)) { - dnsPort = socket.getLocalPort(); - LOG.info("Mock DNS server started on port {}", dnsPort); - - while (started.get()) { - byte[] buffer = new byte[MAX_PACKET_SIZE]; - DatagramPacket packet = new DatagramPacket(buffer, buffer.length); - socket.receive(packet); - - String queryName = extractQueryName(buffer, packet.getLength()); - String txtRecord = txtRecords.get(queryName); - - byte[] response; - if (txtRecord != null) { - response = createTXTResponse(buffer, queryName, txtRecord); - } else { - response = createErrorResponse(buffer, queryName); - } - - final DatagramPacket responsePacket = - new DatagramPacket( - response, response.length, packet.getAddress(), packet.getPort()); - socket.send(responsePacket); - } - } catch (final IOException e) { - throw new UncheckedIOException("Unexpected IO Exception", e); - } - LOG.info("Mock DNS server stopped."); - }); - } - - private String extractQueryName(final byte[] buffer, final int length) { - StringBuilder queryName = new StringBuilder(); - int index = 12; // Skip the DNS header - - while (index < length) { - int labelLength = buffer[index] & 0xFF; - - if (labelLength == 0) { - break; - } - - index++; - - for (int i = 0; i < labelLength; i++) { - char c = (char) (buffer[index + i] & 0xFF); - queryName.append(c); - } - - index += labelLength; - - if (index < length && buffer[index] != 0) { - queryName.append("."); - } - } - - return queryName.toString(); - } - - private byte[] createTXTResponse( - final byte[] queryData, final String queryName, final String txtRecord) { - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - DataOutputStream dataOutputStream = new DataOutputStream(outputStream)) { - - // Write DNS header - dataOutputStream.writeShort(getQueryId(queryData)); // Identifier (based on query) - dataOutputStream.writeShort(0x8180); // Flags (Standard query response, No error) - dataOutputStream.writeShort(1); // Questions count - dataOutputStream.writeShort(1); // Answers count - dataOutputStream.writeShort(0); // Authority RRs count - dataOutputStream.writeShort(0); // Additional RRs count - - // Write query name - Iterable queryLabels = Splitter.on(".").split(queryName); - for (String label : queryLabels) { - dataOutputStream.writeByte(label.length()); - dataOutputStream.writeBytes(label); - } - dataOutputStream.writeByte(0); // End of query name - - // Write query type and class - dataOutputStream.writeShort(16); // Type (TXT) - dataOutputStream.writeShort(1); // Class (IN) - - // Write answer - for (String label : queryLabels) { - dataOutputStream.writeByte(label.length()); - dataOutputStream.writeBytes(label.toLowerCase(Locale.ROOT)); - } - dataOutputStream.writeByte(0); // End of answer name - - dataOutputStream.writeShort(16); // Type (TXT) - dataOutputStream.writeShort(1); // Class (IN) - dataOutputStream.writeInt(60); // TTL (60 seconds) - - byte[] txtRecordBytes = txtRecord.getBytes(UTF_8); - dataOutputStream.writeShort(txtRecordBytes.length + 1); // Data length - dataOutputStream.writeByte(txtRecordBytes.length); // TXT record length - dataOutputStream.write(txtRecordBytes); // TXT record data - - dataOutputStream.flush(); - return outputStream.toByteArray(); - } catch (final IOException e) { - throw new UncheckedIOException("Unexpected IO Exception", e); - } - } - - private byte[] createErrorResponse(final byte[] queryData, final String queryName) { - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - DataOutputStream dataOutputStream = new DataOutputStream(outputStream)) { - - // Write DNS header - dataOutputStream.writeShort(getQueryId(queryData)); // Identifier (random) - dataOutputStream.writeShort(0x8183); // Flags (Standard query response, NXDOMAIN error) - dataOutputStream.writeShort(1); // Questions count - dataOutputStream.writeShort(0); // Answers count - dataOutputStream.writeShort(0); // Authority RRs count - dataOutputStream.writeShort(0); // Additional RRs count - - // Write query name - for (String label : Splitter.on(".").split(queryName)) { - dataOutputStream.writeByte(label.length()); - dataOutputStream.writeBytes(label); - } - dataOutputStream.writeByte(0); // End of query name - - // Write query type and class - dataOutputStream.writeShort(16); // Type (TXT) - dataOutputStream.writeShort(1); // Class (IN) - - dataOutputStream.flush(); - return outputStream.toByteArray(); - } catch (final IOException e) { - throw new UncheckedIOException("Unexpected IO Exception", e); - } - } - - private short getQueryId(final byte[] queryData) { - return (short) ((queryData[0] & 0xff) << 8 | (queryData[1] & 0xff)); - } -} diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/MockDnsServerVerticle.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/MockDnsServerVerticle.java new file mode 100644 index 00000000000..f9bd7ea5cff --- /dev/null +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/MockDnsServerVerticle.java @@ -0,0 +1,205 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.p2p.discovery.dns; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import com.google.common.base.Splitter; +import com.google.common.io.Resources; +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Promise; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.datagram.DatagramPacket; +import io.vertx.core.datagram.DatagramSocket; +import io.vertx.core.datagram.DatagramSocketOptions; +import io.vertx.core.json.JsonObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Mock DNS server verticle. */ +public class MockDnsServerVerticle extends AbstractVerticle { + private static final Logger LOG = LoggerFactory.getLogger(MockDnsServerVerticle.class); + private final Map txtRecords = new HashMap<>(); + private int dnsPort; + + @Override + public void start(final Promise startPromise) throws Exception { + final DatagramSocket datagramSocket = vertx.createDatagramSocket(new DatagramSocketOptions()); + datagramSocket.handler(packet -> handleDatagramPacket(datagramSocket, packet)); + + final String dnsEntriesJsonPath = + Path.of(Resources.getResource("discovery/dns/dns-records.json").toURI()).toString(); + LOG.debug("Reading DNS entries from: {}", dnsEntriesJsonPath); + vertx + .fileSystem() + .readFile(dnsEntriesJsonPath) + .compose( + buffer -> { + final JsonObject dnsEntries = new JsonObject(buffer.toString()); + final Map jsonMap = dnsEntries.getMap(); + jsonMap.forEach((key, value) -> txtRecords.put(key, value.toString())); + + // start the server + return datagramSocket.listen(0, "127.0.0.1"); + }) + .onComplete( + res -> { + if (res.succeeded()) { + LOG.info("Mock Dns Server is now listening {}", res.result().localAddress()); + dnsPort = res.result().localAddress().port(); + startPromise.complete(); + } else { + startPromise.fail(res.cause()); + } + }); + } + + @Override + public void stop() { + LOG.info("Stopping Mock DNS Server"); + } + + private void handleDatagramPacket(final DatagramSocket socket, final DatagramPacket packet) { + LOG.debug("Packet Received"); + Buffer data = packet.data(); + final short queryId = getQueryId(data); + final String queryName = extractQueryName(data.getBytes()); + + final Buffer response; + if (txtRecords.containsKey(queryName)) { + LOG.debug("Query name found {}", queryName); + response = createTXTResponse(queryId, queryName, txtRecords.get(queryName)); + } else { + LOG.debug("Query name not found: {}", queryName); + response = createErrorResponse(queryId, queryName); + } + + socket.send(response, packet.sender().port(), packet.sender().host()); + } + + private String extractQueryName(final byte[] buffer) { + StringBuilder queryName = new StringBuilder(); + int index = 12; // Skip the DNS header + + while (index < buffer.length) { + int labelLength = buffer[index] & 0xFF; + + if (labelLength == 0) { + break; + } + + index++; + + for (int i = 0; i < labelLength; i++) { + char c = (char) (buffer[index + i] & 0xFF); + queryName.append(c); + } + + index += labelLength; + + if (index < buffer.length && buffer[index] != 0) { + queryName.append("."); + } + } + + return queryName.toString(); + } + + private Buffer createTXTResponse( + final short queryId, final String queryName, final String txtRecord) { + final Buffer buffer = Buffer.buffer(); + + // Write DNS header + buffer.appendShort(queryId); // Query Identifier + buffer.appendShort((short) 0x8180); // Flags (Standard query response, No error) + buffer.appendShort((short) 1); // Questions count + buffer.appendShort((short) 1); // Answers count + buffer.appendShort((short) 0); // Authority RRs count + buffer.appendShort((short) 0); // Additional RRs count + + // Write query name + final Iterable queryLabels = Splitter.on(".").split(queryName); + for (String label : queryLabels) { + buffer.appendByte((byte) label.length()); + buffer.appendString(label); + } + buffer.appendByte((byte) 0); // End of query name + + // Write query type and class + buffer.appendShort((short) 16); // Type (TXT) + buffer.appendShort((short) 1); // Class (IN) + + // Write answer + for (String label : queryLabels) { + buffer.appendByte((byte) label.length()); + buffer.appendString(label.toLowerCase(Locale.ROOT)); + } + buffer.appendByte((byte) 0); // End of answer name + + buffer.appendShort((short) 16); // TXT record type + buffer.appendShort((short) 1); // Class (IN) + buffer.appendInt(60); // TTL (60 seconds) + + int txtRecordsLength = txtRecord.getBytes(UTF_8).length; + buffer.appendShort((short) (txtRecordsLength + 1)); // Data length + buffer.appendByte((byte) txtRecordsLength); // TXT record length + buffer.appendString(txtRecord); + + return buffer; + } + + private Buffer createErrorResponse(final short queryId, final String queryName) { + Buffer buffer = Buffer.buffer(); + + // Write DNS header + buffer.appendShort(queryId); // Query Identifier + buffer.appendShort((short) 0x8183); // Flags (Standard query response, NXDOMAIN error) + buffer.appendShort((short) 1); // Questions count + buffer.appendShort((short) 0); // Answers count + buffer.appendShort((short) 0); // Authority RRs count + buffer.appendShort((short) 0); // Additional RRs count + + // Write query name + for (String label : Splitter.on(".").split(queryName)) { + buffer.appendByte((byte) label.length()); + buffer.appendString(label); + } + buffer.appendByte((byte) 0); // End of query name + + // Write query type and class + buffer.appendShort((short) 16); // Type (TXT) + buffer.appendShort((short) 1); // Class (IN) + + return buffer; + } + + private short getQueryId(final Buffer queryData) { + return (short) ((queryData.getByte(0) & 0xff) << 8 | (queryData.getByte(1) & 0xff)); + } + + /** + * Mock server local port + * + * @return server port + */ + public int port() { + return dnsPort; + } +} From 636aedbbd129a32db3d622d7e399afd4e10043b4 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Fri, 31 May 2024 13:46:47 +1000 Subject: [PATCH 25/26] increase second lookup time in test Signed-off-by: Usman Saleem --- .../besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java index 06cb472cc2d..cb465093d07 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java @@ -105,7 +105,7 @@ void testDNSDaemonPeriodic(final Vertx vertx, final VertxTestContext testContext }, 0, 1, // initial delay - 50, // second lookup after 50 ms (due to Mock DNS server, we are very quick). + 300, // second lookup after 300 ms (due to Mock DNS server, we are very quick). "localhost:" + mockDnsServerVerticle.port()); final DeploymentOptions options = From 806bf5b6de3c33a5d89ad4b7e9e6892fa73370bf Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Mon, 3 Jun 2024 12:30:48 +1000 Subject: [PATCH 26/26] Update NOTICE Signed-off-by: Usman Saleem --- NOTICE | 19 +++++++++++++++++++ NOTICE.md | 0 2 files changed, 19 insertions(+) create mode 100644 NOTICE delete mode 100644 NOTICE.md diff --git a/NOTICE b/NOTICE new file mode 100644 index 00000000000..af5acfe009e --- /dev/null +++ b/NOTICE @@ -0,0 +1,19 @@ +Hyperledger Besu +Copyright contributors to Hyperledger Besu. + +This product includes software adapted from Tuweni. (https://tuweni.tmio.io/) +Copyright 2023-2024 The Machine Consultancy LLC +Licensed under Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + +The following NOTICE file content is provided by Tuweni: +------------------------------------------------------------ +This product includes code developed under the Apache Tuweni incubation project. + +Copyright 2019-2023 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + +In addition, this product includes software developed by +Copyright 2018-2019 ConsenSys, Inc. +------------------------------------------------------------ diff --git a/NOTICE.md b/NOTICE.md deleted file mode 100644 index e69de29bb2d..00000000000