Skip to content

Commit 57a4ede

Browse files
Add QUIC support (#193)
1 parent b259700 commit 57a4ede

10 files changed

Lines changed: 112 additions & 10 deletions

File tree

src/main/java/org/ethereum/beacon/discovery/DiscoverySystemBuilder.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,9 +206,16 @@ private void createDefaults() {
206206
newAddress.getAddress() instanceof Inet6Address
207207
? oldRecord.getTcp6Address()
208208
: oldRecord.getTcpAddress();
209+
final Optional<InetSocketAddress> oldQuicAddress =
210+
newAddress.getAddress() instanceof Inet6Address
211+
? oldRecord.getQuic6Address()
212+
: oldRecord.getQuicAddress();
209213
return Optional.of(
210214
oldRecord.withNewAddress(
211-
newAddress, oldTcpAddress.map(InetSocketAddress::getPort), secretKey));
215+
newAddress,
216+
oldTcpAddress.map(InetSocketAddress::getPort),
217+
oldQuicAddress.map(InetSocketAddress::getPort),
218+
secretKey));
212219
});
213220
schedulers = requireNonNullElseGet(schedulers, Schedulers::createDefault);
214221
final List<InetSocketAddress> serverListenAddresses =

src/main/java/org/ethereum/beacon/discovery/schema/EnrField.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,16 @@ public class EnrField {
2020
public static final String TCP = "tcp";
2121
// UDP port, integer
2222
public static final String UDP = "udp";
23+
// QUIC (UDP) port, integer
24+
public static final String QUIC = "quic";
2325
// IPv6 address
2426
public static final String IP_V6 = "ip6";
2527
// IPv6-specific TCP port
2628
public static final String TCP_V6 = "tcp6";
2729
// IPv6-specific UDP port
2830
public static final String UDP_V6 = "udp6";
31+
// IPv6-specific QUIC (UDP) port
32+
public static final String QUIC_V6 = "quic6";
2933

3034
/* ENR v4 Identity Schema */
3135
// Compressed secp256k1 public key, 33 bytes

src/main/java/org/ethereum/beacon/discovery/schema/EnrFieldInterpreterV4.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,12 @@ public EnrFieldInterpreterV4() {
3030
fieldDecoders.put(EnrField.IP_V4, Function.identity());
3131
fieldDecoders.put(EnrField.TCP, fromBytes(bytes -> bytes.toUnsignedBigInteger().intValue()));
3232
fieldDecoders.put(EnrField.UDP, fromBytes(bytes -> bytes.toUnsignedBigInteger().intValue()));
33+
fieldDecoders.put(EnrField.QUIC, fromBytes(bytes -> bytes.toUnsignedBigInteger().intValue()));
3334
fieldDecoders.put(EnrField.IP_V6, Function.identity());
3435
fieldDecoders.put(EnrField.TCP_V6, fromBytes(bytes -> bytes.toUnsignedBigInteger().intValue()));
3536
fieldDecoders.put(EnrField.UDP_V6, fromBytes(bytes -> bytes.toUnsignedBigInteger().intValue()));
37+
fieldDecoders.put(
38+
EnrField.QUIC_V6, fromBytes(bytes -> bytes.toUnsignedBigInteger().intValue()));
3639
}
3740

3841
private static Function<Object, Object> fromBytes(final Function<Bytes, Object> decoder) {

src/main/java/org/ethereum/beacon/discovery/schema/IdentitySchemaInterpreter.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,15 @@ default boolean isValid(NodeRecord nodeRecord) {
4444

4545
Optional<InetSocketAddress> getTcp6Address(NodeRecord nodeRecord);
4646

47+
Optional<InetSocketAddress> getQuicAddress(NodeRecord nodeRecord);
48+
49+
Optional<InetSocketAddress> getQuic6Address(NodeRecord nodeRecord);
50+
4751
NodeRecord createWithNewAddress(
4852
NodeRecord nodeRecord,
4953
InetSocketAddress newAddress,
5054
Optional<Integer> newTcpPort,
55+
Optional<Integer> newQuicPort,
5156
SecretKey secretKey);
5257

5358
NodeRecord createWithUpdatedCustomField(

src/main/java/org/ethereum/beacon/discovery/schema/IdentitySchemaV4Interpreter.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,23 @@ public Optional<InetSocketAddress> getTcp6Address(NodeRecord nodeRecord) {
114114
.or(() -> addressFromFields(nodeRecord, EnrField.IP_V6, EnrField.TCP));
115115
}
116116

117+
@Override
118+
public Optional<InetSocketAddress> getQuicAddress(NodeRecord nodeRecord) {
119+
return addressFromFields(nodeRecord, EnrField.IP_V4, EnrField.QUIC);
120+
}
121+
122+
@Override
123+
public Optional<InetSocketAddress> getQuic6Address(NodeRecord nodeRecord) {
124+
return addressFromFields(nodeRecord, EnrField.IP_V6, EnrField.QUIC_V6)
125+
.or(() -> addressFromFields(nodeRecord, EnrField.IP_V6, EnrField.QUIC));
126+
}
127+
117128
@Override
118129
public NodeRecord createWithNewAddress(
119130
final NodeRecord nodeRecord,
120131
final InetSocketAddress newAddress,
121132
final Optional<Integer> newTcpPort,
133+
final Optional<Integer> newQuicPort,
122134
final SecretKey secretKey) {
123135
final List<EnrField> fields =
124136
getAllFieldsThatMatch(
@@ -132,7 +144,7 @@ public NodeRecord createWithNewAddress(
132144
return !addressFieldsToChange.contains(field.getName());
133145
});
134146
NodeRecordBuilder.addFieldsForAddress(
135-
fields, newAddress.getAddress(), newAddress.getPort(), newTcpPort);
147+
fields, newAddress.getAddress(), newAddress.getPort(), newTcpPort, newQuicPort);
136148
final NodeRecord newRecord = NodeRecord.fromValues(this, nodeRecord.getSeq().add(1), fields);
137149
sign(newRecord, secretKey);
138150
return newRecord;

src/main/java/org/ethereum/beacon/discovery/schema/NodeRecord.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,12 +227,21 @@ public Optional<InetSocketAddress> getUdp6Address() {
227227
return identitySchemaInterpreter.getUdp6Address(this);
228228
}
229229

230+
public Optional<InetSocketAddress> getQuicAddress() {
231+
return identitySchemaInterpreter.getQuicAddress(this);
232+
}
233+
234+
public Optional<InetSocketAddress> getQuic6Address() {
235+
return identitySchemaInterpreter.getQuic6Address(this);
236+
}
237+
230238
public NodeRecord withNewAddress(
231239
final InetSocketAddress newUdpAddress,
232240
final Optional<Integer> newTcpPort,
241+
final Optional<Integer> newQuicPort,
233242
final SecretKey secretKey) {
234243
return identitySchemaInterpreter.createWithNewAddress(
235-
this, newUdpAddress, newTcpPort, secretKey);
244+
this, newUdpAddress, newTcpPort, newQuicPort, secretKey);
236245
}
237246

238247
public NodeRecord withUpdatedCustomField(
@@ -252,10 +261,14 @@ public String toString() {
252261
+ getUdpAddress()
253262
+ ", tcpAddress="
254263
+ getTcpAddress()
264+
+ ", quicAddress="
265+
+ getQuicAddress()
255266
+ ", udp6Address="
256267
+ getUdp6Address()
257268
+ ", tcp6Address="
258269
+ getTcp6Address()
270+
+ ", quic6Address="
271+
+ getQuic6Address()
259272
+ ", asBase64="
260273
+ this.asBase64()
261274
+ ", nodeId="

src/main/java/org/ethereum/beacon/discovery/schema/NodeRecordBuilder.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,22 @@ public NodeRecordBuilder address(final String ipAddress, final int port) {
5353
}
5454

5555
public NodeRecordBuilder address(final String ipAddress, final int udpPort, final int tcpPort) {
56+
return address(ipAddress, udpPort, tcpPort, Optional.empty());
57+
}
58+
59+
public NodeRecordBuilder address(
60+
final String ipAddress, final int udpPort, final int tcpPort, final int quicPort) {
61+
return address(ipAddress, udpPort, tcpPort, Optional.of(quicPort));
62+
}
63+
64+
public NodeRecordBuilder address(
65+
final String ipAddress,
66+
final int udpPort,
67+
final int tcpPort,
68+
final Optional<Integer> quicPort) {
5669
try {
5770
final InetAddress inetAddress = InetAddress.getByName(ipAddress);
58-
addFieldsForAddress(fields, inetAddress, udpPort, Optional.of(tcpPort));
71+
addFieldsForAddress(fields, inetAddress, udpPort, Optional.of(tcpPort), quicPort);
5972
} catch (UnknownHostException e) {
6073
throw new IllegalArgumentException("Unable to resolve address: " + ipAddress);
6174
}
@@ -71,13 +84,16 @@ static void addFieldsForAddress(
7184
final List<EnrField> fields,
7285
final InetAddress inetAddress,
7386
final int udpPort,
74-
final Optional<Integer> newTcpPort) {
87+
final Optional<Integer> newTcpPort,
88+
final Optional<Integer> newQuicPort) {
7589
final Bytes address = Bytes.wrap(inetAddress.getAddress());
7690
final boolean isIpV6 = inetAddress instanceof Inet6Address;
7791
fields.add(new EnrField(isIpV6 ? EnrField.IP_V6 : EnrField.IP_V4, address));
7892
fields.add(new EnrField(isIpV6 ? EnrField.UDP_V6 : EnrField.UDP, udpPort));
7993
newTcpPort.ifPresent(
8094
tcpPort -> fields.add(new EnrField(isIpV6 ? EnrField.TCP_V6 : EnrField.TCP, tcpPort)));
95+
newQuicPort.ifPresent(
96+
quicPort -> fields.add(new EnrField(isIpV6 ? EnrField.QUIC_V6 : EnrField.QUIC, quicPort)));
8197
}
8298

8399
static void addCustomField(

src/test/java/org/ethereum/beacon/discovery/NodeRecordTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,16 @@ void testEnrWithValidEthFieldDecodes() {
250250
assertEquals(Bytes.fromHexString("0x82f4a72b01001020ffffffffffffffff"), nodeRecord.get("eth2"));
251251
}
252252

253+
@Test
254+
void shouldDecodeEnr_withQuicField() {
255+
final String enr =
256+
"enr:-MS4QEyTlybz9KMHKN7pXHOvlD8Q1muUXN7mbeUCPjSyKOEYScKDpmkaxxuE8DOC-YlCIqweCQpEw8uLG4s4z0huDAEUh2F0dG5ldHOIAAAAAAAGAACEZXRoMpBprg6ZBQFwAP__________gmlkgnY0gmlwhIjzqrKEcXVpY4IjKYlzZWNwMjU2azGhA-tFvPZaDfibme3y3o9xderqssFhY1Pnjw6AwqwreQz7iHN5bmNuZXRzAIN0Y3CCIyiDdWRwgiMo";
257+
258+
final NodeRecord nodeRecord = NODE_RECORD_FACTORY.fromEnr(enr);
259+
assertEquals(enr, nodeRecord.asEnr());
260+
assertEquals(9001, nodeRecord.get(EnrField.QUIC));
261+
}
262+
253263
@Test
254264
public void testNodeRecordConstructorFailsToConstructOver300Bytes() {
255265
Assertions.assertThrows(

src/test/java/org/ethereum/beacon/discovery/SimpleIdentitySchemaInterpreter.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ public class SimpleIdentitySchemaInterpreter implements IdentitySchemaInterprete
2626

2727
public static final NewAddressHandler ADDRESS_UPDATER =
2828
(oldRecord, newAddress) ->
29-
Optional.of(oldRecord.withNewAddress(newAddress, Optional.empty(), null));
29+
Optional.of(
30+
oldRecord.withNewAddress(newAddress, Optional.empty(), Optional.empty(), null));
3031

3132
public static NodeRecord createNodeRecord(final int nodeId) {
3233
return createNodeRecord(Bytes.ofUnsignedInt(nodeId));
@@ -99,11 +100,22 @@ public Optional<InetSocketAddress> getTcp6Address(NodeRecord nodeRecord) {
99100
return Optional.empty();
100101
}
101102

103+
@Override
104+
public Optional<InetSocketAddress> getQuicAddress(NodeRecord nodeRecord) {
105+
return Optional.empty();
106+
}
107+
108+
@Override
109+
public Optional<InetSocketAddress> getQuic6Address(NodeRecord nodeRecord) {
110+
return Optional.empty();
111+
}
112+
102113
@Override
103114
public NodeRecord createWithNewAddress(
104115
final NodeRecord nodeRecord,
105116
final InetSocketAddress newAddress,
106117
final Optional<Integer> newTcpPort,
118+
final Optional<Integer> newQuicPort,
107119
final SecretKey secretKey) {
108120
final NodeRecord newRecord = createNodeRecord(getNodeId(nodeRecord), newAddress);
109121
sign(newRecord, secretKey);

src/test/java/org/ethereum/beacon/discovery/schema/IdentitySchemaV4InterpreterTest.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ public void shouldUpdateIpV4AddressAndPort() {
171171
final InetSocketAddress newSocketAddress = new InetSocketAddress("127.0.0.1", 40404);
172172
final NodeRecord newRecord =
173173
interpreter.createWithNewAddress(
174-
initialRecord, newSocketAddress, Optional.of(5667), SECRET_KEY);
174+
initialRecord, newSocketAddress, Optional.of(5667), Optional.empty(), SECRET_KEY);
175175

176176
assertThat(newRecord.getUdpAddress()).contains(newSocketAddress);
177177
assertThat(newRecord.getTcpAddress())
@@ -210,14 +210,31 @@ public void shouldUpdateIpV6AddressAndPort() throws Exception {
210210
new InetSocketAddress(InetAddress.getByAddress(IPV6_LOCALHOST.toArrayUnsafe()), 40404);
211211
final NodeRecord newRecord =
212212
interpreter.createWithNewAddress(
213-
initialRecord, newSocketAddress, Optional.of(5667), SECRET_KEY);
213+
initialRecord, newSocketAddress, Optional.of(5667), Optional.empty(), SECRET_KEY);
214214

215215
assertThat(newRecord.getUdp6Address()).contains(newSocketAddress);
216216
assertThat(newRecord.getTcp6Address())
217217
.contains(new InetSocketAddress(newSocketAddress.getAddress(), 5667));
218218
assertThat(newRecord.get(EnrField.IP_V6)).isEqualTo(IPV6_LOCALHOST);
219219
}
220220

221+
@Test
222+
public void shouldAddQuicPort() throws Exception {
223+
final NodeRecord initialRecord =
224+
createNodeRecord(
225+
new EnrField(EnrField.IP_V4, Bytes.wrap(new byte[4])),
226+
new EnrField(EnrField.UDP, 9000));
227+
final InetSocketAddress newSocketAddress = new InetSocketAddress("127.0.0.1", 40404);
228+
final NodeRecord newRecord =
229+
interpreter.createWithNewAddress(
230+
initialRecord, newSocketAddress, Optional.empty(), Optional.of(9001), SECRET_KEY);
231+
232+
assertThat(newRecord.getUdpAddress()).contains(newSocketAddress);
233+
assertThat(newRecord.get(EnrField.IP_V4))
234+
.isEqualTo(Bytes.wrap(newSocketAddress.getAddress().getAddress()));
235+
assertThat(newRecord.getQuicAddress()).contains(new InetSocketAddress("127.0.0.1", 9001));
236+
}
237+
221238
@Test
222239
public void shouldAddIpV6toAlreadyExistingIpV4() throws Exception {
223240
final NodeRecord initialRecord =
@@ -228,13 +245,16 @@ public void shouldAddIpV6toAlreadyExistingIpV4() throws Exception {
228245
new InetSocketAddress(InetAddress.getByAddress(IPV6_LOCALHOST.toArrayUnsafe()), 40404);
229246
final NodeRecord newRecord =
230247
interpreter.createWithNewAddress(
231-
initialRecord, newSocketAddress, Optional.of(5667), SECRET_KEY);
248+
initialRecord, newSocketAddress, Optional.of(5667), Optional.of(5668), SECRET_KEY);
232249

233250
assertThat(newRecord.getUdpAddress()).isEqualTo(initialRecord.getUdpAddress());
234251
assertThat(newRecord.getUdp6Address()).contains(newSocketAddress);
235252
assertThat(newRecord.getTcpAddress()).isEqualTo(initialRecord.getTcpAddress());
236253
assertThat(newRecord.getTcp6Address())
237254
.contains(new InetSocketAddress(newSocketAddress.getAddress(), 5667));
255+
assertThat(newRecord.getQuicAddress()).isEqualTo(initialRecord.getQuicAddress());
256+
assertThat(newRecord.getQuic6Address())
257+
.contains(new InetSocketAddress(newSocketAddress.getAddress(), 5668));
238258
assertThat(newRecord.get(EnrField.IP_V4)).isEqualTo(initialRecord.get(EnrField.IP_V4));
239259
assertThat(newRecord.get(EnrField.IP_V6)).isEqualTo(IPV6_LOCALHOST);
240260
}
@@ -249,7 +269,7 @@ public void shouldAddIpV4ToAlreadyExistingIpV6() {
249269
final InetSocketAddress newSocketAddress = new InetSocketAddress("127.0.0.1", 40404);
250270
final NodeRecord newRecord =
251271
interpreter.createWithNewAddress(
252-
initialRecord, newSocketAddress, Optional.of(5667), SECRET_KEY);
272+
initialRecord, newSocketAddress, Optional.of(5667), Optional.empty(), SECRET_KEY);
253273

254274
assertThat(newRecord.getUdp6Address()).isEqualTo(initialRecord.getUdp6Address());
255275
assertThat(newRecord.getUdpAddress()).contains(newSocketAddress);

0 commit comments

Comments
 (0)