Add DNSDaemon to listen to DNS entries
diff --git a/dns-discovery/src/integrationTest/java/org/apache/tuweni/discovery/DiscoveryAPITest.java b/dns-discovery/src/integrationTest/java/org/apache/tuweni/discoveryint/DiscoveryAPITest.java
similarity index 91%
rename from dns-discovery/src/integrationTest/java/org/apache/tuweni/discovery/DiscoveryAPITest.java
rename to dns-discovery/src/integrationTest/java/org/apache/tuweni/discoveryint/DiscoveryAPITest.java
index 80ea34d..6de5a8e 100644
--- a/dns-discovery/src/integrationTest/java/org/apache/tuweni/discovery/DiscoveryAPITest.java
+++ b/dns-discovery/src/integrationTest/java/org/apache/tuweni/discoveryint/DiscoveryAPITest.java
@@ -10,11 +10,13 @@
* 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.
*/
-package org.apache.tuweni.discovery;
+package org.apache.tuweni.discoveryint;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.apache.tuweni.devp2p.EthereumNodeRecord;
+import org.apache.tuweni.discovery.DNSResolver;
+import org.apache.tuweni.discovery.DNSVisitor;
import org.apache.tuweni.junit.BouncyCastleExtension;
import java.util.ArrayList;
diff --git a/dns-discovery/src/main/kotlin/org/apache/tuweni/discovery/DNSDaemon.kt b/dns-discovery/src/main/kotlin/org/apache/tuweni/discovery/DNSDaemon.kt
new file mode 100644
index 0000000..17b07f3
--- /dev/null
+++ b/dns-discovery/src/main/kotlin/org/apache/tuweni/discovery/DNSDaemon.kt
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.tuweni.discovery
+
+import org.apache.tuweni.devp2p.EthereumNodeRecord
+import java.util.Timer
+import java.util.TimerTask
+import java.util.concurrent.atomic.AtomicReference
+
+/**
+ * Resolves DNS records over time, refreshing records.
+ *
+ * @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 enrLink the ENR link to start with, of the form enrtree://PUBKEY@domain
+ * @param period the period at which to poll DNS records
+ */
+public class DNSDaemon @JvmOverloads constructor(
+ private val dnsServer: String? = null,
+ private val seq: Long = 0,
+ private val enrLink: String,
+ private val period: Long = 60000L
+) {
+
+ /**
+ * Listeners notified when records are read and whenever they are updated.
+ */
+ val listeners = HashSet<(List<EthereumNodeRecord>) -> Unit>()
+
+ private val timer: Timer = Timer(false)
+ private val records = AtomicReference<EthereumNodeRecord>()
+
+ init {
+ timer.scheduleAtFixedRate(DNSTimerTask(dnsServer, seq, enrLink, this::updateRecords), 0, period)
+ }
+
+ private fun updateRecords(records: List<EthereumNodeRecord>) {
+ listeners.forEach { it(records) }
+ }
+
+ /**
+ * Close the daemon.
+ */
+ public fun close() {
+ timer.cancel()
+ }
+}
+
+class DNSTimerTask(
+ private val dnsServer: String? = null,
+ private var seq: Long,
+ private val enrLink: String,
+ private val records: (List<EthereumNodeRecord>) -> Unit
+) : TimerTask() {
+
+ override fun run() {
+ val resolver = DNSResolver(dnsServer, seq)
+ records(resolver.collectAll(enrLink))
+ seq = resolver.seq
+ }
+}
diff --git a/dns-discovery/src/main/kotlin/org/apache/tuweni/discovery/DNSEntry.kt b/dns-discovery/src/main/kotlin/org/apache/tuweni/discovery/DNSEntry.kt
index 5e3a6bf..085c95c 100644
--- a/dns-discovery/src/main/kotlin/org/apache/tuweni/discovery/DNSEntry.kt
+++ b/dns-discovery/src/main/kotlin/org/apache/tuweni/discovery/DNSEntry.kt
@@ -99,7 +99,7 @@
class ENRTreeRoot(attrs: Map<String, String>) : DNSEntry {
val version: String
- val seq: Int
+ val seq: Long
val sig: SECP256K1.Signature
val enrRoot: String
val linkRoot: String
@@ -110,7 +110,7 @@
}
version = attrs["enrtree-root"]!!
- seq = attrs["seq"]!!.toInt()
+ seq = attrs["seq"]!!.toLong()
val sigBytes = Base64URLSafe.decode(attrs["sig"]!!)
sig = SECP256K1.Signature.fromBytes(Bytes.concatenate(sigBytes,
Bytes.wrap(ByteArray(Math.max(0, 65 - sigBytes.size())))))
diff --git a/dns-discovery/src/main/kotlin/org/apache/tuweni/discovery/DNSResolver.kt b/dns-discovery/src/main/kotlin/org/apache/tuweni/discovery/DNSResolver.kt
index db0d38d..503715d 100644
--- a/dns-discovery/src/main/kotlin/org/apache/tuweni/discovery/DNSResolver.kt
+++ b/dns-discovery/src/main/kotlin/org/apache/tuweni/discovery/DNSResolver.kt
@@ -49,10 +49,13 @@
* Resolves a set of ENR nodes from a host name.
*
* @param dnsServer the DNS server to use for DNS query. If null, the default DNS server will be used.
- * @param signingKey the public key associated with the domain, to check that the root DNS record is valid.
- *
+ * @param seq the sequence number of the root record. If the root record seq is higher, proceed with visit.
*/
-class DNSResolver(private val dnsServer: String? = null, private val signingKey: SECP256K1.PublicKey? = null) {
+
+public class DNSResolver @JvmOverloads constructor(
+ private val dnsServer: String? = null,
+ var seq: Long = 0
+) {
companion object {
val logger = LoggerFactory.getLogger(DNSResolver::class.java)
@@ -116,6 +119,12 @@
logger.debug("ENR tree root ${link.domainName} failed signature check")
return
}
+ if (entry.seq <= seq) {
+ logger.debug("ENR tree root seq $entry.seq is not higher than $seq, aborting")
+ return
+ }
+ seq = entry.seq
+
internalVisit(entry.enrRoot, link.domainName, visitor)
internalVisit(entry.linkRoot, link.domainName, visitor)
}