You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tuweni.apache.org by to...@apache.org on 2020/05/02 22:41:42 UTC
[incubator-tuweni] branch master updated: Implement DNS discovery
reader, reading from goerli and mainnet
This is an automated email from the ASF dual-hosted git repository.
toulmean pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-tuweni.git
The following commit(s) were added to refs/heads/master by this push:
new 6e2fcd5 Implement DNS discovery reader, reading from goerli and mainnet
6e2fcd5 is described below
commit 6e2fcd52eda7fe8096a6eb1d04efbafbc555febf
Author: Antoine Toulme <an...@lunar-ocean.com>
AuthorDate: Sat May 2 15:39:58 2020 -0700
Implement DNS discovery reader, reading from goerli and mainnet
---
dependency-versions.gradle | 1 +
.../org/apache/tuweni/devp2p/EthereumNodeRecord.kt | 36 ++--
dns-discovery/build.gradle | 1 +
.../kotlin/org/apache/tuweni/discovery/DNSEntry.kt | 89 +++++++---
.../org/apache/tuweni/discovery/DNSResolver.kt | 195 +++++++++++++++++++++
.../org/apache/tuweni/discovery/DNSVisitor.kt | 34 ++++
.../org/apache/tuweni/discovery/DNSEntryTest.kt | 37 ++--
.../org/apache/tuweni/discovery/DNSResolverTest.kt | 65 +++++++
8 files changed, 401 insertions(+), 57 deletions(-)
diff --git a/dependency-versions.gradle b/dependency-versions.gradle
index 3afb555..29299d7 100644
--- a/dependency-versions.gradle
+++ b/dependency-versions.gradle
@@ -12,6 +12,7 @@
*/
dependencyManagement {
dependencies {
+ dependency('ch.qos.logback:logback-classic:1.2.3')
dependency('commons-codec:commons-codec:1.14')
dependency('com.fasterxml.jackson.core:jackson-databind:2.9.5')
dependency('com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.9.8')
diff --git a/devp2p/src/main/kotlin/org/apache/tuweni/devp2p/EthereumNodeRecord.kt b/devp2p/src/main/kotlin/org/apache/tuweni/devp2p/EthereumNodeRecord.kt
index 61743a0..cc6f7f7 100644
--- a/devp2p/src/main/kotlin/org/apache/tuweni/devp2p/EthereumNodeRecord.kt
+++ b/devp2p/src/main/kotlin/org/apache/tuweni/devp2p/EthereumNodeRecord.kt
@@ -46,20 +46,26 @@ class EthereumNodeRecord(val signature: Bytes, val seq: Long, val data: Map<Stri
if (rlp.size() > 300) {
throw IllegalArgumentException("Record too long")
}
- return RLP.decodeList(rlp, {
- val sig = it.readValue()
-
- val seq = it.readLong()
-
- val data = mutableMapOf<String, Bytes>()
- while (!it.isComplete) {
- val key = it.readString()
- val value = it.readValue()
- data[key] = value
- }
+ return RLP.decodeList(rlp) {
+ val sig = it.readValue()
+
+ val seq = it.readLong()
+
+ val data = mutableMapOf<String, Bytes>()
+ while (!it.isComplete) {
+ val key = it.readString()
+ if (it.nextIsList()) {
+ it.skipNext()
+ // TODO("not ready yet to read list values")
+ // data[key] = it.readListContents { listreader -> listreader.readValue()}
+ } else {
+ val value = it.readValue()
+ data[key] = value
+ }
+ }
- EthereumNodeRecord(sig, seq, data)
- })
+ EthereumNodeRecord(sig, seq, data)
+ }
}
private fun encode(
@@ -168,6 +174,10 @@ class EthereumNodeRecord(val signature: Bytes, val seq: Long, val data: Map<Stri
fun udp(): Int {
return data["udp"]!!.toInt()
}
+
+ override fun toString(): String {
+ return "enr:${ip()}:${tcp()}?udp=${udp()}"
+ }
}
internal class InvalidNodeRecordException(message: String?) : RuntimeException(message)
diff --git a/dns-discovery/build.gradle b/dns-discovery/build.gradle
index 061a2ff..ce375b8 100644
--- a/dns-discovery/build.gradle
+++ b/dns-discovery/build.gradle
@@ -34,4 +34,5 @@ dependencies {
testCompile 'org.junit.jupiter:junit-jupiter-params'
testRuntime 'org.junit.jupiter:junit-jupiter-engine'
+ testRuntime 'ch.qos.logback:logback-classic'
}
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 51a87fd..6a4a78a 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
@@ -21,35 +21,56 @@ 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.apache.tuweni.rlp.InvalidRLPEncodingException
/**
* Intermediate format to write DNS entries
*/
-internal interface DNSEntry {
+public interface DNSEntry {
companion object {
+ /**
+ * Read a DNS entry from a String.
+ * @param serialized the serialized form of a DNS entry
+ * @return DNS entry if found
+ * @throws InvalidEntryException if the record cannot be read
+ */
fun readDNSEntry(serialized: String): DNSEntry {
- val attrs = serialized.split(" ").map {
+ var record = serialized
+ if (record[0] == '"') {
+ record = record.substring(1, record.length - 1)
+ }
+ if (record.startsWith("enrtree-root")) {
+ return ENRTreeRoot(readKV(record))
+ } else if (record.startsWith("enrtree-branch")) {
+ return ENRTree(record.substring("enrtree-branch:".length))
+ } else if (record.startsWith("enr:")) {
+ return ENRNode(readKV(record))
+ } else if (record.startsWith("enrtree-link:")) {
+ return ENRTreeLink(readKV(record))
+ } else {
+ throw InvalidEntryException("$serialized should contain enrtree-branch, enr, enrtree-root or enrtree-link")
+ }
+ }
+
+ private fun readKV(record: String): Map<String, String> {
+ return record.split(" ").map {
val equalseparator = it.indexOf("=")
if (equalseparator == -1) {
- throw InvalidEntryException("Invalid record entry $serialized")
+ val colonseparator = it.indexOf(":")
+ if (colonseparator == -1) {
+ throw InvalidEntryException("$it could not be read")
+ }
+ val key = it.substring(0, colonseparator)
+ val value = it.substring(colonseparator + 1)
+ Pair(key, value)
+ } else {
+ val key = it.substring(0, equalseparator)
+ val value = it.substring(equalseparator + 1)
+ Pair(key, value)
}
- val key = it.substring(0, equalseparator)
- val value = it.substring(equalseparator + 1)
- Pair(key, value)
}.toMap()
- if (attrs.containsKey("enrtree-root")) {
- return ENRTreeRoot(attrs)
- } else if (attrs.containsKey("enrtree")) {
- return ENRTree(attrs)
- } else if (attrs.containsKey("enr")) {
- return ENRNode(attrs)
- } else if (attrs.containsKey("enrtree-link")) {
- return ENRTreeLink(attrs)
- } else {
- throw InvalidEntryException("$serialized should contain enrtree, enr, enrtree-root or enrtree-link")
- }
}
}
}
@@ -62,7 +83,15 @@ class ENRNode(attrs: Map<String, String>) : DNSEntry {
if (attrs["enr"] == null) {
throw InvalidEntryException("Missing attributes on enr entry")
}
- nodeRecord = EthereumNodeRecord.fromRLP(Base64URLSafe.decode(attrs["enr"]!!))
+ try {
+ nodeRecord = EthereumNodeRecord.fromRLP(Base64URLSafe.decode(attrs["enr"]!!))
+ } catch (e: InvalidRLPEncodingException) {
+ throw InvalidEntryException(e.message)
+ }
+ }
+
+ override fun toString(): String {
+ return nodeRecord.toString()
}
}
@@ -72,34 +101,40 @@ class ENRTreeRoot(attrs: Map<String, String>) : DNSEntry {
val seq: Int
val sig: SECP256K1.Signature
val hash: Bytes
+ val encodedHash: String
+ val link: String
init {
- if (attrs["enrtree-root"] == null || attrs["seq"] == null || attrs["sig"] == null || attrs["hash"] == null) {
+ if (attrs["enrtree-root"] == null || attrs["seq"] == null || attrs["sig"] == null || attrs["e"] == null ||
+ attrs["l"] == null) {
throw InvalidEntryException("Missing attributes on root entry")
}
version = attrs["enrtree-root"]!!
seq = attrs["seq"]!!.toInt()
- sig = SECP256K1.Signature.fromBytes(Base64URLSafe.decode(attrs["sig"]!!))
- hash = Base32.decode(attrs["hash"]!!)
+ val sigBytes = Base64URLSafe.decode(attrs["sig"]!!)
+ sig = SECP256K1.Signature.fromBytes(Bytes.concatenate(sigBytes,
+ Bytes.wrap(ByteArray(Math.max(0, 65 - sigBytes.size())))))
+ encodedHash = attrs["e"]!!
+ hash = Base32.decode(encodedHash)
+ link = attrs["l"]!!
}
override fun toString(): String {
val encodedHash = Base32.encode(hash)
- return "enrtree-root=$version hash=${encodedHash.subSequence(0, encodedHash.indexOf("="))} " +
- "seq=$seq sig=${Base64URLSafe.encode(sig.bytes())}"
+ return "enrtree-root:$version e=${encodedHash.subSequence(0, encodedHash.indexOf("="))} " +
+ "l=$link seq=$seq sig=${Base64URLSafe.encode(sig.bytes())}"
}
}
-class ENRTree(attrs: Map<String, String>) : DNSEntry {
+class ENRTree(entriesAsString: String) : DNSEntry {
val entries: List<String>
init {
- val attr = attrs["enrtree"] ?: throw InvalidEntryException("Missing attributes on enrtree entry")
- entries = attr.split(",")
+ entries = entriesAsString.split(",", "\"").filter { it.length > 4 }
}
override fun toString(): String {
- return "enrtree=${entries.joinToString(",")}"
+ return "enrtree-branch:${entries.joinToString(",")}"
}
}
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
new file mode 100644
index 0000000..4f6ebdd
--- /dev/null
+++ b/dns-discovery/src/main/kotlin/org/apache/tuweni/discovery/DNSResolver.kt
@@ -0,0 +1,195 @@
+/*
+ * 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
+/*
+ * 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.
+ */
+
+import org.apache.tuweni.crypto.SECP256K1
+import org.apache.tuweni.devp2p.EthereumNodeRecord
+import org.slf4j.LoggerFactory
+import org.xbill.DNS.DClass
+import org.xbill.DNS.Message
+import org.xbill.DNS.Name
+import org.xbill.DNS.Record
+import org.xbill.DNS.Section
+import org.xbill.DNS.SimpleResolver
+import org.xbill.DNS.Type
+import org.xbill.DNS.WireParseException
+
+/**
+ * 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.
+ *
+ */
+class DNSResolver(private val dnsServer: String? = null, private val signingKey: SECP256K1.PublicKey? = null) {
+
+ companion object {
+ val logger = LoggerFactory.getLogger(DNSResolver::class.java)
+ }
+
+ /**
+ * 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
+ */
+ public fun resolveRecord(domainName: String): DNSEntry? {
+ return resolveRecordRaw(domainName)?.let { DNSEntry.readDNSEntry(it) }
+ }
+
+ private fun checkSignature(entry: ENRTreeRoot, sig: SECP256K1.Signature): Boolean {
+ if (signingKey == null) {
+ return true
+ }
+ TODO("not implemented, $entry $sig")
+ // val recovered = SECP256K1.PublicKey.recoverFromSignature(entry.signedContent(), sig)
+ // return signingKey.equals(recovered)
+ }
+
+ /**
+ * Convenience method to read all ENRs, from a top-level record.
+ *
+ * @param domainName the DNS domain name to start with
+ * @return all ENRs collected
+ */
+ public fun collectAll(domainName: String): List<EthereumNodeRecord> {
+ val nodes = mutableListOf<EthereumNodeRecord>()
+ val visitor = object : DNSVisitor {
+ override fun visit(enr: EthereumNodeRecord): Boolean {
+ nodes.add(enr)
+ return true
+ }
+ }
+ visitTree(domainName, visitor)
+ return nodes
+ }
+
+ /**
+ * Reads a complete tree of record, starting with the top-level record.
+ * @param domainName the DNS domain name to start with
+ * @param visitor the visitor that will look at each record
+ */
+ public fun visitTree(domainName: String, visitor: DNSVisitor) {
+ val entry = resolveRecord(domainName)
+ if (entry !is ENRTreeRoot) {
+ logger.debug("Root domain name $domainName is not an ENR tree root")
+ return
+ }
+ val linkedStr = resolveRecordRaw("${entry.encodedHash}.$domainName")
+ if (linkedStr == null) {
+ logger.debug("No linked record under ${entry.encodedHash}.$domainName")
+ return
+ }
+ if (!checkSignature(entry, entry.sig)) {
+ logger.debug("ENR tree root $domainName failed signature check")
+ return
+ }
+
+ val linkedEntry = DNSEntry.readDNSEntry(linkedStr)
+ // TODO check hash matches
+ if (linkedEntry is ENRNode) {
+ visitor.visit(linkedEntry.nodeRecord)
+ return
+ } else if (linkedEntry is ENRTree) {
+ for (e in linkedEntry.entries) {
+ if (!internalVisit(e, domainName, visitor)) {
+ break
+ }
+ }
+ } else {
+ logger.debug("No linked record")
+ }
+ }
+
+ /**
+ * Resolves the first TXT record associated with a domain name,
+ * and returns it, or null if no such record exists or the record cannot be read.
+ *
+ * @param domainName the name of the DNS domain to query
+ * @return the first TXT entry of the DNS record
+ */
+ fun resolveRecordRaw(domainName: String): String? {
+ try {
+ val resolver = SimpleResolver(dnsServer)
+ // required as TXT records are quite big, and dnsjava maxes out UDP payload.
+ resolver.setTCP(true)
+ val type = Type.TXT
+ val name = Name.fromString(domainName, Name.root)
+ val rec = Record.newRecord(name, type, DClass.IN)
+ val query = Message.newQuery(rec)
+ val response = resolver.send(query)
+ val records = response.getSectionArray(Section.ANSWER)
+
+ if (records.isNotEmpty()) {
+ return records[0].rdataToString()
+ } else {
+ logger.debug("No TXT record for $domainName")
+ return null
+ }
+ } catch (e: WireParseException) {
+ logger.error("Error reading TXT record", e)
+ return null
+ }
+ }
+
+ private fun internalVisit(entryName: String, domainName: String, visitor: DNSVisitor): Boolean {
+ try {
+ val entry = resolveRecord("$entryName.$domainName")
+ if (entry == null) {
+ return true
+ }
+
+ if (entry is ENRNode) {
+ return visitor.visit(entry.nodeRecord)
+ } else if (entry is ENRTree) {
+ for (e in entry.entries) {
+ val keepGoing = internalVisit(e, domainName, visitor)
+ if (!keepGoing) {
+ return false
+ }
+ }
+ } else if (entry is ENRTreeLink) {
+ visitTree(entry.domainName, visitor)
+ } else {
+ logger.debug("Unsupported type of node $entry")
+ }
+ return true
+ } catch (e: InvalidEntryException) {
+ logger.warn(e.message, e)
+ return true
+ } catch (e: IllegalArgumentException) {
+ logger.warn(e.message, e)
+ return true
+ }
+ }
+}
diff --git a/dns-discovery/src/main/kotlin/org/apache/tuweni/discovery/DNSVisitor.kt b/dns-discovery/src/main/kotlin/org/apache/tuweni/discovery/DNSVisitor.kt
new file mode 100644
index 0000000..b398807
--- /dev/null
+++ b/dns-discovery/src/main/kotlin/org/apache/tuweni/discovery/DNSVisitor.kt
@@ -0,0 +1,34 @@
+/*
+ * 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
+
+/**
+ * Reads ENR (Ethereum Node Records) entries passed in from DNS.
+ *
+ * The visitor may decide to stop the visit by returning false.
+ */
+interface DNSVisitor {
+
+ /**
+ * Visit a new ENR record.
+ * @param enr the ENR record read from DNS
+ * @return true to continue visiting, false otherwise
+ */
+ fun visit(enr: EthereumNodeRecord): Boolean
+}
diff --git a/dns-discovery/src/test/kotlin/org/apache/tuweni/discovery/DNSEntryTest.kt b/dns-discovery/src/test/kotlin/org/apache/tuweni/discovery/DNSEntryTest.kt
index ce74144..dc13597 100644
--- a/dns-discovery/src/test/kotlin/org/apache/tuweni/discovery/DNSEntryTest.kt
+++ b/dns-discovery/src/test/kotlin/org/apache/tuweni/discovery/DNSEntryTest.kt
@@ -31,7 +31,7 @@ class DNSEntryTest {
val exception: InvalidEntryException = assertThrows {
DNSEntry.readDNSEntry("garbage")
}
- assertEquals("Invalid record entry garbage", exception.message)
+ assertEquals("garbage should contain enrtree-branch, enr, enrtree-root or enrtree-link", exception.message)
}
@Test
@@ -39,7 +39,7 @@ class DNSEntryTest {
val exception: InvalidEntryException = assertThrows {
DNSEntry.readDNSEntry("garbage=abc def")
}
- assertEquals("Invalid record entry garbage=abc def", exception.message)
+ assertNotNull(exception)
}
@Test
@@ -47,7 +47,8 @@ class DNSEntryTest {
val exception: InvalidEntryException = assertThrows {
DNSEntry.readDNSEntry("garbage=abc def=gfh")
}
- assertEquals("garbage=abc def=gfh should contain enrtree, enr, enrtree-root or enrtree-link", exception.message)
+ assertEquals("garbage=abc def=gfh should contain enrtree-branch, enr, enrtree-root or enrtree-link",
+ exception.message)
}
@Test
@@ -61,16 +62,16 @@ class DNSEntryTest {
@Test
fun missingSeqEntry() {
val exception: InvalidEntryException = assertThrows {
- DNSEntry.readDNSEntry("enrtree-root=v1 hash=TO4Q75OQ2N7DX4EOOR7X66A6OM " +
- "sig=N-YY6UB9xD0hFx1Gmnt7v0RfSxch5tKyry2SRDoLx7B4GfPXagwLxQqyf7gAMvApFn_ORwZQekMWa_pXrcGCtwE=")
+ DNSEntry.readDNSEntry("enrtree-root=v1 e=TO4Q75OQ2N7DX4EOOR7X66A6OM l=TO4Q75OQ2N7DX4EOOR7X66A6OM" +
+ " sig=N-YY6UB9xD0hFx1Gmnt7v0RfSxch5tKyry2SRDoLx7B4GfPXagwLxQqyf7gAMvApFn_ORwZQekMWa_pXrcGCtwE=")
}
assertEquals("Missing attributes on root entry", exception.message)
}
@Test
fun testValidENRTreeRoot() {
- val entry = DNSEntry.readDNSEntry("enrtree-root=v1 hash=TO4Q75OQ2N7DX4EOOR7X66A6OM " +
- "seq=3 sig=N-YY6UB9xD0hFx1Gmnt7v0RfSxch5tKyry2SRDoLx7B4GfPXagwLxQqyf7gAMvApFn_ORwZQekMWa_pXrcGCtwE=")
+ val entry = DNSEntry.readDNSEntry("enrtree-root:v1 e=TO4Q75OQ2N7DX4EOOR7X66A6OM l=TO4Q75OQ2N7DX4EOOR7X66A6OM" +
+ " seq=3 sig=N-YY6UB9xD0hFx1Gmnt7v0RfSxch5tKyry2SRDoLx7B4GfPXagwLxQqyf7gAMvApFn_ORwZQekMWa_pXrcGCtwE=")
as ENRTreeRoot
assertEquals("v1", entry.version)
assertEquals(3, entry.seq)
@@ -79,15 +80,15 @@ class DNSEntryTest {
@Test
fun testValidENRTreeLink() {
val entry = DNSEntry.readDNSEntry(
- "enrtree-link=AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org")
+ "enrtree-link:morenodes.example.org")
as ENRTreeLink
- assertEquals("AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org", entry.domainName)
+ assertEquals("morenodes.example.org", entry.domainName)
}
@Test
fun testValidENRNode() {
- val entry = DNSEntry.readDNSEntry("enr=-H24QI0fqW39CMBZjJvV-EJZKyBYIoqvh69kfkF4X8DsJuXOZC6emn53SrrZD8P4v9Wp7Nxg" +
- "DYwtEUs3zQkxesaGc6UBgmlkgnY0gmlwhMsAcQGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOA==")
+ val entry = DNSEntry.readDNSEntry("enr:-H24QI0fqW39CMBZjJvV-EJZKyBYIoqvh69kfkF4X8DsJuXOZC6emn53SrrZD8P4v9Wp7Nxg" +
+ "DYwtEUs3zQkxesaGc6UBgmlkgnY0gmlwhMsAcQGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOA")
val enr = entry as ENRNode
val nodeRecord = enr.nodeRecord
assertNotNull(nodeRecord)
@@ -96,7 +97,7 @@ class DNSEntryTest {
@Test
fun testValidENRTreeNode() {
- val entry = DNSEntry.readDNSEntry("enrtree=F4YWVKW4N6B2DDZWFS4XCUQBHY,JTNOVTCP6XZUMXDRANXA6SWXTM," +
+ val entry = DNSEntry.readDNSEntry("enrtree-branch:F4YWVKW4N6B2DDZWFS4XCUQBHY,JTNOVTCP6XZUMXDRANXA6SWXTM," +
"JGUFMSAGI7KZYB3P7IZW4S5Y3A")
val enr = entry as ENRTree
val entries = enr.entries
@@ -106,24 +107,26 @@ class DNSEntryTest {
@Test
fun testRootToString() {
- val root = ENRTreeRoot(mapOf(Pair("enrtree-root", "v1"), Pair("hash", "TO4Q75OQ2N7DX4EOOR7X66A6OM"),
+ val root = ENRTreeRoot(mapOf(Pair("enrtree-root", "v1"), Pair("l", "TO4Q75OQ2N7DX4EOOR7X66A6OM"),
+ Pair("e", "TO4Q75OQ2N7DX4EOOR7X66A6OM"),
Pair("seq", "3"),
Pair("sig", "N-YY6UB9xD0hFx1Gmnt7v0RfSxch5tKyry2SRDoLx7B4GfPXagwLxQqyf7gAMvApFn_ORwZQekMWa_pXrcGCtwE=")))
- assertEquals("enrtree-root=v1 hash=TO4Q75OQ2N7DX4EOOR7X66A6OM seq=3 sig=N-YY6UB9xD0hFx1Gmnt7v0RfSxch5tKyry" +
+ assertEquals("enrtree-root:v1 e=TO4Q75OQ2N7DX4EOOR7X66A6OM " +
+ "l=TO4Q75OQ2N7DX4EOOR7X66A6OM seq=3 sig=N-YY6UB9xD0hFx1Gmnt7v0RfSxch5tKyry" +
"2SRDoLx7B4GfPXagwLxQqyf7gAMvApFn_ORwZQekMWa_pXrcGCtwE=", root.toString())
}
@Test
fun testEntryToString() {
- val entry = DNSEntry.readDNSEntry("enrtree=F4YWVKW4N6B2DDZWFS4XCUQBHY,JTNOVTCP6XZUMXDRANXA6SWXTM," +
+ val entry = DNSEntry.readDNSEntry("enrtree-branch:F4YWVKW4N6B2DDZWFS4XCUQBHY,JTNOVTCP6XZUMXDRANXA6SWXTM," +
"JGUFMSAGI7KZYB3P7IZW4S5Y3A")
- assertEquals("enrtree=F4YWVKW4N6B2DDZWFS4XCUQBHY,JTNOVTCP6XZUMXDRANXA6SWXTM,JGUFMSAGI7KZYB3P7IZW4S5Y3A",
+ assertEquals("enrtree-branch:F4YWVKW4N6B2DDZWFS4XCUQBHY,JTNOVTCP6XZUMXDRANXA6SWXTM,JGUFMSAGI7KZYB3P7IZW4S5Y3A",
entry.toString())
}
@Test
fun testEntryLinkToString() {
- val entry = DNSEntry.readDNSEntry("enrtree-link=AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7B" +
+ val entry = DNSEntry.readDNSEntry("enrtree-link:AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7B" +
"VDQ5FDPRT2@morenodes.example.org")
assertEquals("enrtree-link=AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org",
entry.toString())
diff --git a/dns-discovery/src/test/kotlin/org/apache/tuweni/discovery/DNSResolverTest.kt b/dns-discovery/src/test/kotlin/org/apache/tuweni/discovery/DNSResolverTest.kt
new file mode 100644
index 0000000..a5b4bf8
--- /dev/null
+++ b/dns-discovery/src/test/kotlin/org/apache/tuweni/discovery/DNSResolverTest.kt
@@ -0,0 +1,65 @@
+/*
+ * 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 org.apache.tuweni.junit.BouncyCastleExtension
+import org.junit.jupiter.api.Assertions.assertNotNull
+import org.junit.jupiter.api.Assertions.assertTrue
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+
+@ExtendWith(BouncyCastleExtension::class)
+class DNSResolverTest {
+
+ @Test
+ fun testQueryTXT() {
+ val resolver = DNSResolver()
+ val rec = resolver.resolveRecordRaw("all.goerli.ethdisco.net")
+ assertNotNull(rec)
+ }
+
+ @Test
+ fun resolveAllGoerliNodes() {
+ val resolver = DNSResolver()
+ val nodes = mutableListOf<EthereumNodeRecord>()
+ val visitor = object : DNSVisitor {
+ override fun visit(enr: EthereumNodeRecord): Boolean {
+ nodes.add(enr)
+ return true
+ }
+ }
+
+ resolver.visitTree("all.goerli.ethdisco.net", visitor)
+ assertTrue(nodes.size > 0)
+ }
+
+ @Test
+ fun resolveAllMainnetNodes() {
+ val resolver = DNSResolver()
+ val nodes = mutableListOf<EthereumNodeRecord>()
+ val visitor = object : DNSVisitor {
+ override fun visit(enr: EthereumNodeRecord): Boolean {
+ nodes.add(enr)
+ return true
+ }
+ }
+
+ resolver.visitTree("all.mainnet.ethdisco.net", visitor)
+ assertTrue(nodes.size > 0)
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tuweni.apache.org
For additional commands, e-mail: commits-help@tuweni.apache.org