Skip to content
This repository was archived by the owner on Jul 1, 2025. It is now read-only.

Commit 52d99f6

Browse files
usmansaleemgaryschulte
authored andcommitted
Reimplement EthereumNodeRecord for DNS discovery (#7989)
* Reimplement EthereumNodeRecord and remove dependency on tuweni-devp2p * Refactor EthereumNodeRecord for DNSDaemon * Update EthereumNodeRecord to use Besu RLP * additional unit tests * Convert ENR to Java record * regenerate equals and hashcode for enr record --------- Signed-off-by: Usman Saleem <[email protected]>
1 parent cb25c5b commit 52d99f6

File tree

14 files changed

+244
-24
lines changed

14 files changed

+244
-24
lines changed

crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/AbstractSECP256.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,11 @@ public String getProvider() {
233233
return PROVIDER;
234234
}
235235

236+
@Override
237+
public ECDomainParameters getCurve() {
238+
return curve;
239+
}
240+
236241
/**
237242
* Gets K calculator.
238243
*

crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithm.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import org.apache.tuweni.bytes.Bytes;
2222
import org.apache.tuweni.bytes.Bytes32;
23+
import org.bouncycastle.crypto.params.ECDomainParameters;
2324
import org.bouncycastle.math.ec.ECPoint;
2425

2526
/** The interface Signature algorithm. */
@@ -124,6 +125,13 @@ SECPSignature normaliseSignature(
124125
*/
125126
String getCurveName();
126127

128+
/**
129+
* Bouncy castle ECDomainParameters representing the curve.
130+
*
131+
* @return instance of ECDomainParameters
132+
*/
133+
ECDomainParameters getCurve();
134+
127135
/**
128136
* Create secp private key.
129137
*

ethereum/p2p/build.gradle

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,6 @@ dependencies {
4646

4747
implementation 'io.tmio:tuweni-bytes'
4848
implementation 'io.tmio:tuweni-crypto'
49-
implementation('io.tmio:tuweni-devp2p') {
50-
exclude group:'ch.qos.logback', module:'logback-classic'
51-
}
5249
implementation 'io.tmio:tuweni-io'
5350
implementation 'io.tmio:tuweni-rlp'
5451
implementation 'io.tmio:tuweni-units'

ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import java.util.Optional;
1919

2020
import io.vertx.core.AbstractVerticle;
21-
import org.apache.tuweni.devp2p.EthereumNodeRecord;
2221
import org.slf4j.Logger;
2322
import org.slf4j.LoggerFactory;
2423

ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonListener.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
import java.util.List;
1818

19-
import org.apache.tuweni.devp2p.EthereumNodeRecord;
20-
2119
// Adapted from https://github.com/tmio/tuweni and licensed under Apache 2.0
2220
/** Callback listening to updates of the DNS records. */
2321
@FunctionalInterface

ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntry.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525

2626
import org.apache.tuweni.bytes.Bytes;
2727
import org.apache.tuweni.crypto.SECP256K1;
28-
import org.apache.tuweni.devp2p.EthereumNodeRecord;
2928
import org.apache.tuweni.io.Base32;
3029
import org.apache.tuweni.io.Base64URLSafe;
3130
import org.bouncycastle.math.ec.ECPoint;

ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import org.apache.tuweni.bytes.Bytes;
3434
import org.apache.tuweni.bytes.Bytes32;
3535
import org.apache.tuweni.crypto.SECP256K1;
36-
import org.apache.tuweni.devp2p.EthereumNodeRecord;
3736
import org.slf4j.Logger;
3837
import org.slf4j.LoggerFactory;
3938

ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSVisitor.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414
*/
1515
package org.hyperledger.besu.ethereum.p2p.discovery.dns;
1616

17-
import org.apache.tuweni.devp2p.EthereumNodeRecord;
18-
1917
// Adapted from https://github.com/tmio/tuweni and licensed under Apache 2.0
2018
/**
2119
* Reads ENR (Ethereum Node Records) entries passed in from DNS. The visitor may decide to stop the
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*
2+
* Copyright contributors to Hyperledger Besu.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*
13+
* SPDX-License-Identifier: Apache-2.0
14+
*/
15+
16+
// Adapted from https://github.com/tmio/tuweni and licensed under Apache 2.0
17+
package org.hyperledger.besu.ethereum.p2p.discovery.dns;
18+
19+
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
20+
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput;
21+
22+
import java.net.InetAddress;
23+
import java.nio.charset.StandardCharsets;
24+
import java.util.Arrays;
25+
import java.util.HashMap;
26+
import java.util.Map;
27+
import java.util.Objects;
28+
import java.util.Optional;
29+
30+
import org.apache.tuweni.bytes.Bytes;
31+
32+
/**
33+
* A modified implementation of Ethereum Node Record (ENR) that is used by DNSResolver. See <a
34+
* href="https://eips.ethereum.org/EIPS/eip-778">EIP-778</a>
35+
*/
36+
public record EthereumNodeRecord(
37+
Bytes rlp, Bytes publicKey, InetAddress ip, Optional<Integer> tcp, Optional<Integer> udp) {
38+
39+
/**
40+
* Creates an ENR from its serialized form as a RLP list
41+
*
42+
* @param rlp the serialized form of the ENR
43+
* @return the ENR
44+
* @throws IllegalArgumentException if the rlp bytes length is longer than 300 bytes
45+
*/
46+
public static EthereumNodeRecord fromRLP(final Bytes rlp) {
47+
if (rlp.size() > 300) {
48+
throw new IllegalArgumentException("Record too long");
49+
}
50+
var data = new HashMap<String, Bytes>();
51+
52+
// rlp: sig, sequence, k1,v1, k2,v2, k3, [v3, vn]...
53+
var input = new BytesValueRLPInput(rlp, false);
54+
input.enterList();
55+
56+
input.skipNext(); // skip signature
57+
input.skipNext(); // skip sequence
58+
59+
// go through rest of the list
60+
while (!input.isEndOfCurrentList()) {
61+
var key = new String(input.readBytes().toArrayUnsafe(), StandardCharsets.UTF_8);
62+
if (input.nextIsList()) {
63+
// skip list as we currently don't need any of these complex structures
64+
input.skipNext();
65+
} else {
66+
data.put(key, input.readBytes());
67+
}
68+
}
69+
70+
input.leaveList();
71+
72+
var publicKey = initPublicKeyBytes(data);
73+
74+
return new EthereumNodeRecord(rlp, publicKey, initIPAddr(data), initTCP(data), initUDP(data));
75+
}
76+
77+
/**
78+
* Returns the public key of the ENR
79+
*
80+
* @return the public key of the ENR
81+
*/
82+
static Bytes initPublicKeyBytes(final Map<String, Bytes> data) {
83+
var keyBytes = data.get("secp256k1");
84+
if (keyBytes == null) {
85+
throw new IllegalArgumentException("Missing secp256k1 entry in ENR");
86+
}
87+
// convert 33 bytes compressed public key to uncompressed using Bouncy Castle
88+
var curve = SignatureAlgorithmFactory.getInstance().getCurve();
89+
var ecPoint = curve.getCurve().decodePoint(keyBytes.toArrayUnsafe());
90+
// uncompressed public key is 65 bytes, first byte is 0x04.
91+
var encodedPubKey = ecPoint.getEncoded(false);
92+
return Bytes.of(Arrays.copyOfRange(encodedPubKey, 1, encodedPubKey.length));
93+
}
94+
95+
/**
96+
* Returns the InetAddress of the ENR
97+
*
98+
* @return The IP address of the ENR
99+
*/
100+
static InetAddress initIPAddr(final Map<String, Bytes> data) {
101+
var ipBytes = data.get("ip");
102+
if (ipBytes != null) {
103+
try {
104+
return InetAddress.getByAddress(ipBytes.toArrayUnsafe());
105+
} catch (final Exception e) {
106+
throw new RuntimeException(e);
107+
}
108+
}
109+
return InetAddress.getLoopbackAddress();
110+
}
111+
112+
/**
113+
* The TCP port of the ENR
114+
*
115+
* @return the TCP port associated with this ENR
116+
*/
117+
static Optional<Integer> initTCP(final Map<String, Bytes> data) {
118+
var tcpBytes = data.get("tcp");
119+
return tcpBytes != null ? Optional.of(tcpBytes.toInt()) : Optional.empty();
120+
}
121+
122+
/**
123+
* The UDP port of the ENR. If the UDP port is not present, the TCP port is used.
124+
*
125+
* @return the UDP port associated with this ENR
126+
*/
127+
static Optional<Integer> initUDP(final Map<String, Bytes> data) {
128+
var udpBytes = data.get("udp");
129+
return udpBytes != null ? Optional.of(udpBytes.toInt()) : initTCP(data);
130+
}
131+
132+
/**
133+
* @return the ENR as a URI
134+
*/
135+
@Override
136+
public String toString() {
137+
return "enr:" + ip() + ":" + tcp() + "?udp=" + udp();
138+
}
139+
140+
/** Override equals method to compare the RLP bytes */
141+
@Override
142+
public boolean equals(final Object o) {
143+
if (!(o instanceof EthereumNodeRecord that)) {
144+
return false;
145+
}
146+
return Objects.equals(rlp, that.rlp);
147+
}
148+
149+
/** Override hashCode method to use hashCode of the RLP bytes */
150+
@Override
151+
public int hashCode() {
152+
return Objects.hashCode(rlp);
153+
}
154+
}

ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetwork.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.hyperledger.besu.ethereum.p2p.discovery.VertxPeerDiscoveryAgent;
3030
import org.hyperledger.besu.ethereum.p2p.discovery.dns.DNSDaemon;
3131
import org.hyperledger.besu.ethereum.p2p.discovery.dns.DNSDaemonListener;
32+
import org.hyperledger.besu.ethereum.p2p.discovery.dns.EthereumNodeRecord;
3233
import org.hyperledger.besu.ethereum.p2p.discovery.internal.PeerTable;
3334
import org.hyperledger.besu.ethereum.p2p.peers.DefaultPeerPrivileges;
3435
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl;
@@ -82,7 +83,6 @@
8283
import io.vertx.core.ThreadingModel;
8384
import io.vertx.core.Vertx;
8485
import org.apache.tuweni.bytes.Bytes;
85-
import org.apache.tuweni.devp2p.EthereumNodeRecord;
8686
import org.slf4j.Logger;
8787
import org.slf4j.LoggerFactory;
8888

@@ -366,9 +366,9 @@ DNSDaemonListener createDaemonListener() {
366366
final EnodeURL enodeURL =
367367
EnodeURLImpl.builder()
368368
.ipAddress(enr.ip())
369-
.nodeId(enr.publicKey().bytes())
370-
.discoveryPort(Optional.ofNullable(enr.udp()))
371-
.listeningPort(Optional.ofNullable(enr.tcp()))
369+
.nodeId(enr.publicKey())
370+
.discoveryPort(enr.udp())
371+
.listeningPort(enr.tcp())
372372
.build();
373373
final DiscoveryPeer peer = DiscoveryPeer.fromEnode(enodeURL);
374374
peers.add(peer);

0 commit comments

Comments
 (0)