diff --git a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/AbstractSECP256.java b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/AbstractSECP256.java
index 4e228441cc4..2098dfeadc1 100644
--- a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/AbstractSECP256.java
+++ b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/AbstractSECP256.java
@@ -233,6 +233,11 @@ public String getProvider() {
return PROVIDER;
}
+ @Override
+ public ECDomainParameters getCurve() {
+ return curve;
+ }
+
/**
* Gets K calculator.
*
diff --git a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithm.java b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithm.java
index a1a79d057a5..1d077d51d26 100644
--- a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithm.java
+++ b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithm.java
@@ -20,6 +20,7 @@
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
+import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.math.ec.ECPoint;
/** The interface Signature algorithm. */
@@ -124,6 +125,13 @@ SECPSignature normaliseSignature(
*/
String getCurveName();
+ /**
+ * Bouncy castle ECDomainParameters representing the curve.
+ *
+ * @return instance of ECDomainParameters
+ */
+ ECDomainParameters getCurve();
+
/**
* Create secp private key.
*
diff --git a/ethereum/p2p/build.gradle b/ethereum/p2p/build.gradle
index 6ed42425e0a..f4eb5066284 100644
--- a/ethereum/p2p/build.gradle
+++ b/ethereum/p2p/build.gradle
@@ -46,9 +46,6 @@ dependencies {
implementation 'io.tmio:tuweni-bytes'
implementation 'io.tmio:tuweni-crypto'
- implementation('io.tmio:tuweni-devp2p') {
- 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
index ef794ae2047..7c2d9350f62 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,7 +18,6 @@
import java.util.Optional;
import io.vertx.core.AbstractVerticle;
-import org.apache.tuweni.devp2p.EthereumNodeRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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
index cfa51d4eb73..81629268a8a 100644
--- 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
@@ -16,8 +16,6 @@
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
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 cd439eea063..810bd3b41b1 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
@@ -25,7 +25,6 @@
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;
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 0be4ca619d1..c3347aa8d9e 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
@@ -33,7 +33,6 @@
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;
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
index c6ea0a77ed7..fc9b5af9965 100644
--- 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
@@ -14,8 +14,6 @@
*/
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
diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/EthereumNodeRecord.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/EthereumNodeRecord.java
new file mode 100644
index 00000000000..339afcffca3
--- /dev/null
+++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/EthereumNodeRecord.java
@@ -0,0 +1,154 @@
+/*
+ * 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
+ */
+
+// Adapted from https://github.com/tmio/tuweni and licensed under Apache 2.0
+package org.hyperledger.besu.ethereum.p2p.discovery.dns;
+
+import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
+import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput;
+
+import java.net.InetAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import org.apache.tuweni.bytes.Bytes;
+
+/**
+ * A modified implementation of Ethereum Node Record (ENR) that is used by DNSResolver. See EIP-778
+ */
+public record EthereumNodeRecord(
+ Bytes rlp, Bytes publicKey, InetAddress ip, Optional tcp, Optional udp) {
+
+ /**
+ * Creates an ENR from its serialized form as a RLP list
+ *
+ * @param rlp the serialized form of the ENR
+ * @return the ENR
+ * @throws IllegalArgumentException if the rlp bytes length is longer than 300 bytes
+ */
+ public static EthereumNodeRecord fromRLP(final Bytes rlp) {
+ if (rlp.size() > 300) {
+ throw new IllegalArgumentException("Record too long");
+ }
+ var data = new HashMap();
+
+ // rlp: sig, sequence, k1,v1, k2,v2, k3, [v3, vn]...
+ var input = new BytesValueRLPInput(rlp, false);
+ input.enterList();
+
+ input.skipNext(); // skip signature
+ input.skipNext(); // skip sequence
+
+ // go through rest of the list
+ while (!input.isEndOfCurrentList()) {
+ var key = new String(input.readBytes().toArrayUnsafe(), StandardCharsets.UTF_8);
+ if (input.nextIsList()) {
+ // skip list as we currently don't need any of these complex structures
+ input.skipNext();
+ } else {
+ data.put(key, input.readBytes());
+ }
+ }
+
+ input.leaveList();
+
+ var publicKey = initPublicKeyBytes(data);
+
+ return new EthereumNodeRecord(rlp, publicKey, initIPAddr(data), initTCP(data), initUDP(data));
+ }
+
+ /**
+ * Returns the public key of the ENR
+ *
+ * @return the public key of the ENR
+ */
+ static Bytes initPublicKeyBytes(final Map data) {
+ var keyBytes = data.get("secp256k1");
+ if (keyBytes == null) {
+ throw new IllegalArgumentException("Missing secp256k1 entry in ENR");
+ }
+ // convert 33 bytes compressed public key to uncompressed using Bouncy Castle
+ var curve = SignatureAlgorithmFactory.getInstance().getCurve();
+ var ecPoint = curve.getCurve().decodePoint(keyBytes.toArrayUnsafe());
+ // uncompressed public key is 65 bytes, first byte is 0x04.
+ var encodedPubKey = ecPoint.getEncoded(false);
+ return Bytes.of(Arrays.copyOfRange(encodedPubKey, 1, encodedPubKey.length));
+ }
+
+ /**
+ * Returns the InetAddress of the ENR
+ *
+ * @return The IP address of the ENR
+ */
+ static InetAddress initIPAddr(final Map data) {
+ var ipBytes = data.get("ip");
+ if (ipBytes != null) {
+ try {
+ return InetAddress.getByAddress(ipBytes.toArrayUnsafe());
+ } catch (final Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return InetAddress.getLoopbackAddress();
+ }
+
+ /**
+ * The TCP port of the ENR
+ *
+ * @return the TCP port associated with this ENR
+ */
+ static Optional initTCP(final Map data) {
+ var tcpBytes = data.get("tcp");
+ return tcpBytes != null ? Optional.of(tcpBytes.toInt()) : Optional.empty();
+ }
+
+ /**
+ * The UDP port of the ENR. If the UDP port is not present, the TCP port is used.
+ *
+ * @return the UDP port associated with this ENR
+ */
+ static Optional initUDP(final Map data) {
+ var udpBytes = data.get("udp");
+ return udpBytes != null ? Optional.of(udpBytes.toInt()) : initTCP(data);
+ }
+
+ /**
+ * @return the ENR as a URI
+ */
+ @Override
+ public String toString() {
+ return "enr:" + ip() + ":" + tcp() + "?udp=" + udp();
+ }
+
+ /** Override equals method to compare the RLP bytes */
+ @Override
+ public boolean equals(final Object o) {
+ if (!(o instanceof EthereumNodeRecord that)) {
+ return false;
+ }
+ return Objects.equals(rlp, that.rlp);
+ }
+
+ /** Override hashCode method to use hashCode of the RLP bytes */
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(rlp);
+ }
+}
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 610ebd39d8a..bf8e76b6215 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
@@ -29,6 +29,7 @@
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.dns.EthereumNodeRecord;
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;
@@ -82,7 +83,6 @@
import io.vertx.core.ThreadingModel;
import io.vertx.core.Vertx;
import org.apache.tuweni.bytes.Bytes;
-import org.apache.tuweni.devp2p.EthereumNodeRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -366,9 +366,9 @@ DNSDaemonListener createDaemonListener() {
final EnodeURL enodeURL =
EnodeURLImpl.builder()
.ipAddress(enr.ip())
- .nodeId(enr.publicKey().bytes())
- .discoveryPort(Optional.ofNullable(enr.udp()))
- .listeningPort(Optional.ofNullable(enr.tcp()))
+ .nodeId(enr.publicKey())
+ .discoveryPort(enr.udp())
+ .listeningPort(enr.tcp())
.build();
final DiscoveryPeer peer = DiscoveryPeer.fromEnode(enodeURL);
peers.add(peer);
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 94d9c75e4ae..b25148126c4 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
@@ -14,6 +14,8 @@
*/
package org.hyperledger.besu.ethereum.p2p.discovery.dns;
+import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl;
+
import java.security.Security;
import java.util.concurrent.atomic.AtomicInteger;
@@ -67,10 +69,24 @@ void testDNSDaemon(final Vertx vertx, final VertxTestContext testContext) {
testContext.failNow(
"Expecting 115 records in first pass but got: " + records.size());
}
+ records.forEach(
+ enr -> {
+ try {
+ // make sure enode url can be built from record
+ EnodeURLImpl.builder()
+ .ipAddress(enr.ip())
+ .nodeId(enr.publicKey())
+ .discoveryPort(enr.udp())
+ .listeningPort(enr.tcp())
+ .build();
+ } catch (final Exception e) {
+ testContext.failNow(e);
+ }
+ });
checkpoint.flag();
},
0,
- 0,
+ 1L,
0,
"localhost:" + mockDnsServerVerticle.port());
diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/EthereumNodeRecordTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/EthereumNodeRecordTest.java
new file mode 100644
index 00000000000..da5cfd0f31c
--- /dev/null
+++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/EthereumNodeRecordTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright contributors to 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 static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.net.InetAddress;
+import java.util.Random;
+
+import org.apache.tuweni.bytes.Bytes;
+import org.junit.jupiter.api.Test;
+
+class EthereumNodeRecordTest {
+
+ @Test
+ void buildFromRLP() throws Exception {
+ final Bytes rlp =
+ Bytes.fromHexString(
+ "0xf8a3b84033b8a07e5c8e19dc8ac2529354b21a6c09e5516335eb57c383924aa0ca73434c0c65d8625eb05236e172fcc00d80e913506bde5446fb5c55ea2035380c97480a86018d56dc241083657468c7c6849b192ad0808269648276348269708441157e4389736563703235366b31a102a48c4c032f4c2e1b4007dd15b0d7046b60774f6bc38e2f52a8e0361c65e4234284736e6170c08374637082765f8375647082765f");
+ // method under test
+ final EthereumNodeRecord enr = EthereumNodeRecord.fromRLP(rlp);
+ // expected values
+ final InetAddress expectedIPAddr =
+ InetAddress.getByAddress(Bytes.fromHexString("0x41157e43").toArrayUnsafe());
+ final Bytes expectedPublicKey =
+ Bytes.fromHexString(
+ "0xa48c4c032f4c2e1b4007dd15b0d7046b60774f6bc38e2f52a8e0361c65e423424520b07898c59a8c9e85c440594ca734f23b7f2b906d5da54676eee6a1d64874");
+
+ // assertions
+ assertThat(enr.ip()).isEqualTo(expectedIPAddr);
+ assertThat(enr.publicKey()).isEqualTo(expectedPublicKey);
+ assertThat(enr.tcp()).isNotEmpty().contains(30303);
+ assertThat(enr.udp()).isNotEmpty().contains(30303);
+ }
+
+ @Test
+ void buildFromRLPWithSizeGreaterThan300() {
+ final Bytes rlp = Bytes.random(301, new Random(1L));
+ assertThatThrownBy(() -> EthereumNodeRecord.fromRLP(rlp))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Record too long");
+ }
+}
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index c3acee03312..1a36b557aa2 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -2883,14 +2883,6 @@
-
-
-
-
-
-
-
-
diff --git a/platform/build.gradle b/platform/build.gradle
index f5bb54afcd1..cffc33659ff 100644
--- a/platform/build.gradle
+++ b/platform/build.gradle
@@ -110,7 +110,6 @@ dependencies {
api 'io.tmio:tuweni-config:2.4.2'
api 'io.tmio:tuweni-concurrent:2.4.2'
api 'io.tmio:tuweni-crypto:2.4.2'
- api 'io.tmio:tuweni-devp2p:2.4.2'
api 'io.tmio:tuweni-io:2.4.2'
api 'io.tmio:tuweni-net:2.4.2'
api 'io.tmio:tuweni-rlp:2.4.2'