Skip to content

Commit 937903c

Browse files
committed
add tests
1 parent dfc260d commit 937903c

2 files changed

Lines changed: 111 additions & 5 deletions

File tree

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

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package org.ethereum.beacon.discovery;
66

7+
import java.net.Inet6Address;
78
import java.net.InetAddress;
89
import java.net.InetSocketAddress;
910
import java.net.UnknownHostException;
@@ -35,12 +36,30 @@ public static NodeRecord createNodeRecord(final int nodeId) {
3536

3637
public static NodeRecord createNodeRecord(
3738
final Bytes nodeId, final InetSocketAddress udpAddress) {
39+
if (udpAddress.getAddress() instanceof Inet6Address) {
40+
return createNodeRecord(
41+
nodeId,
42+
new EnrField(EnrField.IP_V6, Bytes.wrap(udpAddress.getAddress().getAddress())),
43+
new EnrField(EnrField.UDP_V6, udpAddress.getPort()));
44+
}
3845
return createNodeRecord(
3946
nodeId,
4047
new EnrField(EnrField.IP_V4, Bytes.wrap(udpAddress.getAddress().getAddress())),
4148
new EnrField(EnrField.UDP, udpAddress.getPort()));
4249
}
4350

51+
public static NodeRecord createDualStackNodeRecord(
52+
final Bytes nodeId,
53+
final InetSocketAddress ipv4Address,
54+
final InetSocketAddress ipv6Address) {
55+
return createNodeRecord(
56+
nodeId,
57+
new EnrField(EnrField.IP_V4, Bytes.wrap(ipv4Address.getAddress().getAddress())),
58+
new EnrField(EnrField.UDP, ipv4Address.getPort()),
59+
new EnrField(EnrField.IP_V6, Bytes.wrap(ipv6Address.getAddress().getAddress())),
60+
new EnrField(EnrField.UDP_V6, ipv6Address.getPort()));
61+
}
62+
4463
public static NodeRecord createNodeRecord(final Bytes nodeId, final EnrField... extraFields) {
4564
final List<EnrField> fields = new ArrayList<>(List.of(extraFields));
4665
fields.add(new EnrField(EnrField.ID, IdentitySchema.V4));
@@ -86,8 +105,18 @@ public Optional<InetSocketAddress> getUdpAddress(final NodeRecord nodeRecord) {
86105
}
87106

88107
@Override
89-
public Optional<InetSocketAddress> getUdp6Address(NodeRecord nodeRecord) {
90-
return Optional.empty();
108+
public Optional<InetSocketAddress> getUdp6Address(final NodeRecord nodeRecord) {
109+
try {
110+
final Bytes ipBytes = (Bytes) nodeRecord.get(EnrField.IP_V6);
111+
if (ipBytes == null) {
112+
return Optional.empty();
113+
}
114+
final InetAddress ipAddress = InetAddress.getByAddress(ipBytes.toArrayUnsafe());
115+
final int port = (int) nodeRecord.get(EnrField.UDP_V6);
116+
return Optional.of(new InetSocketAddress(ipAddress, port));
117+
} catch (UnknownHostException e) {
118+
return Optional.empty();
119+
}
91120
}
92121

93122
@Override
@@ -117,7 +146,31 @@ public NodeRecord createWithNewAddress(
117146
final Optional<Integer> newTcpPort,
118147
final Optional<Integer> newQuicPort,
119148
final Signer signer) {
120-
final NodeRecord newRecord = createNodeRecord(getNodeId(nodeRecord), newAddress);
149+
final List<EnrField> fields = new ArrayList<>();
150+
// Preserve fields from the other IP family
151+
if (newAddress.getAddress() instanceof Inet6Address) {
152+
// Updating IPv6 — preserve IPv4 fields if present
153+
if (nodeRecord.get(EnrField.IP_V4) != null) {
154+
fields.add(new EnrField(EnrField.IP_V4, nodeRecord.get(EnrField.IP_V4)));
155+
fields.add(new EnrField(EnrField.UDP, nodeRecord.get(EnrField.UDP)));
156+
}
157+
fields.add(
158+
new EnrField(EnrField.IP_V6, Bytes.wrap(newAddress.getAddress().getAddress())));
159+
fields.add(new EnrField(EnrField.UDP_V6, newAddress.getPort()));
160+
} else {
161+
// Updating IPv4 — preserve IPv6 fields if present
162+
fields.add(
163+
new EnrField(EnrField.IP_V4, Bytes.wrap(newAddress.getAddress().getAddress())));
164+
fields.add(new EnrField(EnrField.UDP, newAddress.getPort()));
165+
if (nodeRecord.get(EnrField.IP_V6) != null) {
166+
fields.add(new EnrField(EnrField.IP_V6, nodeRecord.get(EnrField.IP_V6)));
167+
fields.add(new EnrField(EnrField.UDP_V6, nodeRecord.get(EnrField.UDP_V6)));
168+
}
169+
}
170+
fields.add(new EnrField(EnrField.ID, IdentitySchema.V4));
171+
fields.add(new EnrField(EnrField.PKEY_SECP256K1, getNodeId(nodeRecord)));
172+
final NodeRecord newRecord =
173+
NodeRecord.fromValues(this, nodeRecord.getSeq().add(1), fields);
121174
sign(newRecord, signer);
122175
return newRecord;
123176
}

src/test/java/org/ethereum/beacon/discovery/message/handler/DefaultExternalAddressSelectorTest.java

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ class DefaultExternalAddressSelectorTest {
2727
private static final InetSocketAddress ADDRESS1 = new InetSocketAddress("127.0.0.1", 2000);
2828
private static final InetSocketAddress ADDRESS2 = new InetSocketAddress("127.0.0.2", 2002);
2929
private static final InetSocketAddress ADDRESS3 = new InetSocketAddress("127.0.0.3", 2003);
30+
private static final InetSocketAddress IPV6_ADDRESS1 = new InetSocketAddress("::1", 3000);
31+
private static final InetSocketAddress IPV6_ADDRESS2 = new InetSocketAddress("::2", 3002);
3032
private static final Instant START_TIME = Instant.ofEpochSecond(1_000_000);
3133
private final Bytes nodeId = Bytes.fromHexString("0x1234567890");
3234
private final NodeRecord originalNodeRecord =
@@ -141,7 +143,58 @@ void shouldRemoveStaleAddresses() {
141143
assertSelectedAddress(ADDRESS3);
142144
}
143145

144-
private void assertSelectedAddress(final InetSocketAddress address2) {
145-
assertThat(localNodeRecordStore.getLocalNodeRecord().getUdpAddress()).contains(address2);
146+
@Test
147+
void shouldAutoDiscoverBothIpv4AndIpv6Addresses() {
148+
// IPv4 gets enough confirmations
149+
for (int i = 0; i < MIN_CONFIRMATIONS; i++) {
150+
selector.onExternalAddressReport(Optional.empty(), ADDRESS2, START_TIME);
151+
}
152+
assertSelectedAddress(ADDRESS2);
153+
154+
// IPv6 also gets enough confirmations
155+
for (int i = 0; i < MIN_CONFIRMATIONS; i++) {
156+
selector.onExternalAddressReport(Optional.empty(), IPV6_ADDRESS1, START_TIME);
157+
}
158+
// Both families should be present in the ENR
159+
assertSelectedAddress(ADDRESS2);
160+
assertSelectedIpv6Address(IPV6_ADDRESS1);
161+
}
162+
163+
@Test
164+
void shouldAutoDiscoverIpv6EvenWhenIpv4HasMoreVotes() {
165+
// IPv4 accumulates many more votes than IPv6
166+
for (int i = 0; i < MIN_CONFIRMATIONS * 10; i++) {
167+
selector.onExternalAddressReport(Optional.empty(), ADDRESS2, START_TIME);
168+
}
169+
assertSelectedAddress(ADDRESS2);
170+
171+
// IPv6 gets just enough confirmations
172+
for (int i = 0; i < MIN_CONFIRMATIONS; i++) {
173+
selector.onExternalAddressReport(Optional.empty(), IPV6_ADDRESS1, START_TIME);
174+
}
175+
// IPv6 should still be discovered despite IPv4 having far more votes
176+
assertSelectedAddress(ADDRESS2);
177+
assertSelectedIpv6Address(IPV6_ADDRESS1);
178+
}
179+
180+
@Test
181+
void shouldSelectMostVotedIpv6AddressIndependentlyFromIpv4() {
182+
// Two competing IPv6 addresses
183+
for (int i = 0; i < MIN_CONFIRMATIONS; i++) {
184+
selector.onExternalAddressReport(Optional.empty(), IPV6_ADDRESS1, START_TIME);
185+
}
186+
for (int i = 0; i < MIN_CONFIRMATIONS + 1; i++) {
187+
selector.onExternalAddressReport(Optional.empty(), IPV6_ADDRESS2, START_TIME);
188+
}
189+
// IPV6_ADDRESS2 should win (more votes)
190+
assertSelectedIpv6Address(IPV6_ADDRESS2);
191+
}
192+
193+
private void assertSelectedAddress(final InetSocketAddress address) {
194+
assertThat(localNodeRecordStore.getLocalNodeRecord().getUdpAddress()).contains(address);
195+
}
196+
197+
private void assertSelectedIpv6Address(final InetSocketAddress address) {
198+
assertThat(localNodeRecordStore.getLocalNodeRecord().getUdp6Address()).contains(address);
146199
}
147200
}

0 commit comments

Comments
 (0)