Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
6e61642
Add DiscV5 IPv4/IPv6 dual-stack support
usmansaleem Feb 9, 2026
811584c
Merge remote-tracking branch 'upstream/main' into discv5_ipv6
usmansaleem Feb 9, 2026
6ca1b4c
Add missing javadoc
usmansaleem Feb 9, 2026
9aeb4ff
Merge remote-tracking branch 'upstream/main' into discv5_ipv6
usmansaleem Feb 9, 2026
8c3e9f4
Handle IPv6-only p2p-host by populating correct ENR fields
usmansaleem Feb 9, 2026
ad3b8a6
Refactor initializeLocalNode to use HostEndpoint record
usmansaleem Feb 9, 2026
ea59cf6
Only query NAT for IPv4 addresses during node initialization
usmansaleem Feb 9, 2026
5ca7eff
Only add IPv6 ENR fields for true dual-stack configurations
usmansaleem Feb 9, 2026
56de007
Fix ipv6FieldsMatch validation for IPv6 primary endpoint
usmansaleem Feb 9, 2026
242f755
Merge remote-tracking branch 'upstream/main' into discv5_ipv6
usmansaleem Feb 9, 2026
cdecf3a
Merge remote-tracking branch 'upstream/main' into discv5_ipv6
usmansaleem Feb 9, 2026
86f28c0
Refactor CLI to use dedicated IPv6 flags instead of comma-separated l…
usmansaleem Feb 10, 2026
f42172f
Merge remote-tracking branch 'upstream/main' into discv5_ipv6
usmansaleem Feb 10, 2026
6a56ff8
Merge remote-tracking branch 'upstream/main' into discv5_ipv6
usmansaleem Feb 10, 2026
c559f3b
Add outbound IP version preference for peer connections
usmansaleem Feb 11, 2026
54fe841
Merge remote-tracking branch 'upstream/main' into discv5_ipv6
usmansaleem Feb 12, 2026
769ed86
Merge remote-tracking branch 'upstream/main' into discv5_ipv6
usmansaleem Feb 16, 2026
a34307a
Merge remote-tracking branch 'upstream/main' into discv5_ipv6
usmansaleem Feb 17, 2026
feb41fd
Add IP version validation for dual-stack configuration
usmansaleem Feb 17, 2026
a859654
Add smart default for IPv6 interface in dual-stack configuration
usmansaleem Feb 17, 2026
2dc3b22
Merge remote-tracking branch 'upstream/main' into discv5_ipv6
usmansaleem Feb 17, 2026
7278066
Refactor P2P discovery options and introduce IPv6 port constant
usmansaleem Feb 17, 2026
43cafd4
Merge remote-tracking branch 'upstream/main' into discv5_ipv6
usmansaleem Feb 17, 2026
3ed2c86
Merge remote-tracking branch 'upstream/main' into discv5_ipv6
usmansaleem Feb 18, 2026
d89b3ea
Fix logging message for discovery agent v5 start method
usmansaleem Feb 18, 2026
8f62c67
Merge remote-tracking branch 'upstream/main' into discv5_ipv6
usmansaleem Feb 18, 2026
7c09f3c
Merge remote-tracking branch 'upstream/main' into discv5_ipv6
usmansaleem Feb 19, 2026
cdbbf1d
Fix javadoc style in IPv6 RunnerBuilder methods
usmansaleem Feb 19, 2026
82f7f92
Fix javadoc verb style for outboundIpVersionPreference method
usmansaleem Feb 19, 2026
87e3761
Replace .get() with orElseThrow() in DiscoveryPeerFactory for IPv6 ad…
usmansaleem Feb 20, 2026
a35f7f1
Fallback to IPv4 ports when IPv6 ports absent in ENR
usmansaleem Feb 20, 2026
2f5d932
Remove unused fromNodeRecord(NodeRecord) overload in DiscoveryPeerFac…
usmansaleem Feb 20, 2026
cb3cc15
Log actual p2pInterfaceIpv6 value instead of constant in auto-set mes…
usmansaleem Feb 20, 2026
343c54e
Add descriptive message to orElseThrow() in NodeRecordManager IPv6 va…
usmansaleem Feb 20, 2026
716655f
Simplify P2PDiscoveryOptions validation methods
usmansaleem Feb 20, 2026
83da373
Replace IpVersionPreference enum with boolean preferIpv6Outbound flag
usmansaleem Feb 20, 2026
7fa1760
Merge remote-tracking branch 'upstream/main' into discv5_ipv6
usmansaleem Feb 20, 2026
1c84175
Update misleading DiscV5 log message in DefaultP2PNetwork
usmansaleem Feb 20, 2026
5906952
Warn when ephemeral ports are used with DiscV5 enabled
usmansaleem Feb 20, 2026
ed588f7
Warn when IPv6 dual-stack options are used with DiscV4
usmansaleem Feb 20, 2026
34c54bb
Fix missing DiscoveryConfiguration import in DefaultP2PNetwork
usmansaleem Feb 20, 2026
ade04ac
Replace p2p-outbound-ip-version with p2p-ipv6-outbound-enabled in config
usmansaleem Feb 20, 2026
d1dd055
Merge remote-tracking branch 'upstream/main' into discv5_ipv6
usmansaleem Feb 22, 2026
b70d3db
Add changelog entry for DiscV5 IPv6 dual-stack support
usmansaleem Feb 22, 2026
d613040
Add RLPx IPv6 dual-stack TCP binding and fix DiscV5 ENR TCP port
usmansaleem Feb 22, 2026
5e40f59
Fix IPv6 TCP port handling in ENR and RLPx startup
usmansaleem Feb 22, 2026
01fb6f8
Refactor ConnectionInitializer.start() to return ListeningAddresses r…
usmansaleem Feb 23, 2026
1f9a06a
Clarify ENR port sourcing in initializeLocalNodeRecord javadoc
usmansaleem Feb 23, 2026
a26c7cc
Refactor NodeRecordManager: split primaryAddressMatches into IPv4/IPv…
usmansaleem Feb 23, 2026
9350263
Merge remote-tracking branch 'upstream/main' into discv5_ipv6
usmansaleem Feb 23, 2026
87878fe
Merge branch 'discv5_ipv6' into rlpx_dual_stack
usmansaleem Feb 23, 2026
612a3ff
Merge remote-tracking branch 'upstream/main' into rlpx_dual_stack
usmansaleem Feb 23, 2026
0c2d434
fix(discv5): fix newAddressHandler intent and remove stale ephemeral …
usmansaleem Feb 23, 2026
3f86c3d
fix(discv5): resolve ephemeral UDP ports in ENR after bind
usmansaleem Feb 23, 2026
a01a237
Merge remote-tracking branch 'upstream/main' into rlpx_dual_stack
usmansaleem Feb 23, 2026
990bd28
refactor(discv5): replace TODO with issue reference for address filte…
usmansaleem Feb 23, 2026
2911935
refactor(discv5): extract hasEphemeralPort helper to reduce duplicati…
usmansaleem Feb 23, 2026
76ef84b
refactor(discv5): remove unused onDiscoveryPortResolved(int, boolean)…
usmansaleem Feb 23, 2026
c3e76c3
test(rlpx): add NettyConnectionInitializerTest for dual-stack binding
usmansaleem Feb 23, 2026
f0314a2
fix(rlpx): log warning when IPv6 socket fails to close cleanly
usmansaleem Feb 23, 2026
4291aa4
Merge remote-tracking branch 'upstream/main' into rlpx_dual_stack
usmansaleem Feb 23, 2026
b835879
refactor: remove ephemeral UDP port resolution (Fix 3) from this branch
usmansaleem Feb 23, 2026
e9b618f
changelog: add entries for RLPx IPv6 dual-stack and ENR tcp/tcp6 fix
usmansaleem Feb 24, 2026
d75269d
refactor(discv5): use TODO(url) style for addressAccessPolicy pending…
usmansaleem Feb 24, 2026
4b14606
fix(rlpx): gate IPv6 dual-stack TCP binding on DiscV5
usmansaleem Feb 24, 2026
3039482
Merge remote-tracking branch 'upstream/main' into rlpx_fix1_fix2
usmansaleem Feb 24, 2026
1ecb2ba
refactor(runner): clarify DiscV5 gate comment for RLPx dual-stack
usmansaleem Feb 24, 2026
2966b69
docs(discv5): document newAddressHandler behaviour and IPv6 enhanceme…
usmansaleem Feb 24, 2026
c7d88ab
docs(discv5): reference issue #9874 in newAddressHandler comment
usmansaleem Feb 24, 2026
fb7be98
Merge remote-tracking branch 'upstream/main' into rlpx_fix1_fix2
usmansaleem Feb 24, 2026
4d058e7
chore(import fix)
usmansaleem Feb 24, 2026
84a0f0c
fix(rlpx): address Copilot review comments on NettyConnectionInitializer
usmansaleem Feb 24, 2026
eeccf17
Merge remote-tracking branch 'upstream/main' into rlpx_fix1_fix2
usmansaleem Feb 24, 2026
c8b946e
fix(rlpx): address self-review findings from PR #9873
usmansaleem Feb 24, 2026
e2f28f0
docs(rlpx): address second-round Copilot review comments
usmansaleem Feb 24, 2026
7b447ca
Merge remote-tracking branch 'upstream/main' into rlpx_fix1_fix2
usmansaleem Feb 24, 2026
36e1082
fix(rlpx): address Copilot review comments on dual-stack shutdown and…
usmansaleem Feb 24, 2026
cf24b82
ci: trigger CI
usmansaleem Feb 24, 2026
031c36b
Merge remote-tracking branch 'upstream/main' into rlpx_fix1_fix2
usmansaleem Feb 24, 2026
18d4e9a
Merge remote-tracking branch 'upstream/main' into rlpx_fix1_fix2
usmansaleem Feb 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@


### Additions and Improvements
- Add IPv6 dual-stack support for DiscV5 peer discovery (enabled via `--Xv5-discovery-enabled`): new `--p2p-host-ipv6`, `--p2p-interface-ipv6`, and `--p2p-port-ipv6` CLI options enable a second UDP discovery socket; `--p2p-ipv6-outbound-enabled` controls whether IPv6 is preferred for outbound connections when a peer advertises both address families [#9763](https://github.com/hyperledger/besu/pull/9763)
- Add IPv6 dual-stack support for DiscV5 peer discovery (enabled via `--Xv5-discovery-enabled`): new `--p2p-host-ipv6`, `--p2p-interface-ipv6`, and `--p2p-port-ipv6` CLI options enable a second UDP discovery socket; `--p2p-ipv6-outbound-enabled` controls whether IPv6 is preferred for outbound connections when a peer advertises both address families [#9763](https://github.com/hyperledger/besu/pull/9763); RLPx now also binds a second TCP socket on the IPv6 interface so IPv6-only peers can establish connections [#9873](https://github.com/hyperledger/besu/pull/9873)
- Support substring and glob matching for `--test-name` in `block-test` evmtool subcommand [#9790](https://github.com/hyperledger/besu/pull/9790)
- Improve performance of snap sync chain download [#9510](https://github.com/hyperledger/besu/pull/9510) and [#9621](https://github.com/hyperledger/besu/pull/9621)
- Add ability to pass a custom tracer to block simulation [#9708](https://github.com/hyperledger/besu/pull/9708)
Expand All @@ -55,6 +55,7 @@
- EVM optimisations - Improve SAR, SHR and SHL opcodes performance [#9796](https://github.com/hyperledger/besu/pull/9796)

### Bug fixes
- Fix DiscV5 ENR `tcp`/`tcp6` fields: previously these reflected the UDP discovery bind port instead of the actual RLPx listening port; ENR initialisation is now deferred until the RLPx TCP port is known [#9873](https://github.com/hyperledger/besu/pull/9873)
- Fix QBFT Shanghai support by reintroducing NotApplicableWithdrawals withdrawals validator [#9830](https://github.com/hyperledger/besu/pull/9830)
- Fix callTracer handling of failed CREATE operations, including correct input field extraction and proper error reporting for both soft failures and revert reasons
- Upgrade netty to 4.2.10-Final - Fixes `setsockopt() failed: Protocol not available` [#9783](https://github.com/hyperledger/besu/pull/9783)
Expand Down
11 changes: 11 additions & 0 deletions app/src/main/java/org/hyperledger/besu/RunnerBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -706,10 +706,21 @@ public Runner build() {
.flatMap(protocolManager -> protocolManager.getSupportedCapabilities().stream())
.collect(Collectors.toSet());

// IPv6 dual-stack support (a second UDP socket + a second TCP socket) was introduced
// alongside DiscV5. Besu does not implement dual-stack for DiscV4, so RLPx should only
// bind a second TCP socket when DiscV5 is active. This guard can be dropped once DiscV4
// is removed.
final boolean rlpxDualStackEnabled =
discoveryEnabled && networkingConfiguration.discoveryConfiguration().isDiscoveryV5Enabled();
final RlpxConfiguration rlpxConfiguration =
RlpxConfiguration.create()
.setBindHost(p2pListenInterface)
.setBindPort(p2pListenPort)
.setBindHostIpv6(rlpxDualStackEnabled ? p2pListenInterfaceIpv6 : Optional.empty())
.setBindPortIpv6(
rlpxDualStackEnabled
? p2pListenInterfaceIpv6.map(ignored -> p2pListenPortIpv6)
: Optional.empty())
.setSupportedProtocols(subProtocols)
.setClientId(BesuVersionUtils.nodeName(identityString));
networkingConfiguration =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

public class RlpxConfiguration {
public static final float DEFAULT_FRACTION_REMOTE_CONNECTIONS_ALLOWED = 0.6f;
private String clientId = "TestClient/1.0.0";
private String bindHost = NetworkUtility.INADDR_ANY;
private int bindPort = 30303;
private Optional<String> bindHostIpv6 = Optional.empty();
private Optional<Integer> bindPortIpv6 = Optional.empty();
private List<SubProtocol> supportedProtocols = Collections.emptyList();

public static RlpxConfiguration create() {
Expand Down Expand Up @@ -65,6 +68,28 @@ public RlpxConfiguration setBindPort(final int bindPort) {
return this;
}

public Optional<String> getBindHostIpv6() {
return bindHostIpv6;
}

public RlpxConfiguration setBindHostIpv6(final Optional<String> bindHostIpv6) {
this.bindHostIpv6 = bindHostIpv6;
return this;
}

public Optional<Integer> getBindPortIpv6() {
return bindPortIpv6;
}

public RlpxConfiguration setBindPortIpv6(final Optional<Integer> bindPortIpv6) {
this.bindPortIpv6 = bindPortIpv6;
return this;
}

public boolean isDualStackEnabled() {
return bindHostIpv6.isPresent() && bindPortIpv6.isPresent();
}

public String getClientId() {
return clientId;
}
Expand All @@ -83,19 +108,24 @@ public boolean equals(final Object o) {
return false;
}
final RlpxConfiguration that = (RlpxConfiguration) o;
return bindPort == that.bindPort && Objects.equals(bindHost, that.bindHost);
return bindPort == that.bindPort
&& Objects.equals(bindHost, that.bindHost)
&& Objects.equals(bindHostIpv6, that.bindHostIpv6)
&& Objects.equals(bindPortIpv6, that.bindPortIpv6);
}

@Override
public int hashCode() {
return Objects.hash(bindHost, bindPort);
return Objects.hash(bindHost, bindPort, bindHostIpv6, bindPortIpv6);
}

@Override
public String toString() {
final StringBuilder sb = new StringBuilder("RlpxConfiguration{");
sb.append("bindHost='").append(bindHost).append('\'');
sb.append(", bindPort=").append(bindPort);
bindHostIpv6.ifPresent(h -> sb.append(", bindHostIpv6='").append(h).append('\''));
bindPortIpv6.ifPresent(p -> sb.append(", bindPortIpv6=").append(p));
sb.append('}');
return sb.toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,45 +14,25 @@
*/
package org.hyperledger.besu.ethereum.p2p.discovery.discv5;

import org.hyperledger.besu.crypto.SECPPublicKey;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.cryptoservices.NodeKey;
import org.hyperledger.besu.ethereum.forkid.ForkIdManager;
import org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration;
import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration;
import org.hyperledger.besu.ethereum.p2p.discovery.DiscoveryPeer;
import org.hyperledger.besu.ethereum.p2p.discovery.HostEndpoint;
import org.hyperledger.besu.ethereum.p2p.discovery.NodeRecordManager;
import org.hyperledger.besu.ethereum.p2p.discovery.PeerDiscoveryAgent;
import org.hyperledger.besu.ethereum.p2p.discovery.PeerDiscoveryAgentFactory;
import org.hyperledger.besu.ethereum.p2p.rlpx.RlpxAgent;
import org.hyperledger.besu.ethereum.storage.StorageProvider;
import org.hyperledger.besu.nat.NatService;

import java.net.InetSocketAddress;
import java.util.Optional;

import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.ethereum.beacon.discovery.AddressAccessPolicy;
import org.ethereum.beacon.discovery.DiscoverySystemBuilder;
import org.ethereum.beacon.discovery.MutableDiscoverySystem;
import org.ethereum.beacon.discovery.schema.NodeRecord;

/**
* Factory for creating DiscV5 {@link PeerDiscoveryAgent} instances backed by the Ethereum Discovery
* v5 {@link DiscoverySystemBuilder}.
*
* <p>This factory is responsible for:
* Factory for creating DiscV5 {@link PeerDiscoveryAgent} instances.
*
* <ul>
* <li>Initializing the local {@link NodeRecord} via {@link NodeRecordManager}
* <li>Configuring and building a mutable DiscV5 discovery system
* <li>Wiring Besu-specific components such as fork ID handling and node key services
* </ul>
* <p>This factory is responsible for wiring together the dependencies needed by {@link
* PeerDiscoveryAgentV5}. It intentionally does <em>not</em> initialize the local node record or
* build the {@link org.ethereum.beacon.discovery.MutableDiscoverySystem} — both are deferred to
* {@link PeerDiscoveryAgentV5#start(int)}, where the actual RLPx TCP port is known.
*
* <p>The resulting {@link PeerDiscoveryAgent} integrates DiscV5 discovery with Besus P2P
* <p>The resulting {@link PeerDiscoveryAgent} integrates DiscV5 discovery with Besu's P2P
* networking stack.
*/
public final class PeerDiscoveryAgentFactoryV5 implements PeerDiscoveryAgentFactory {
Expand Down Expand Up @@ -85,116 +65,23 @@ public PeerDiscoveryAgentFactoryV5(
}

/**
* Creates and configures a DiscV5 {@link PeerDiscoveryAgent}.
* Creates a DiscV5 {@link PeerDiscoveryAgent}.
*
* <p>The local node record and discovery system are built lazily during {@link
* PeerDiscoveryAgentV5#start(int)} so that the ENR {@code tcp}/{@code tcp6} fields receive the
* actual RLPx TCP port rather than the discovery bind port.
*
* @param rlpxAgent the RLPx agent
* @return a fully configured DiscV5 peer discovery agent
* @throws IllegalStateException if the local node record has not been initialized
* @return a configured DiscV5 peer discovery agent ready to be started
*/
@Override
public PeerDiscoveryAgent create(final RlpxAgent rlpxAgent) {
final NodeRecord localNodeRecord = initializeLocalNodeRecord();

final DiscoveryConfiguration disc = config.discoveryConfiguration();
final DiscoverySystemBuilder builder =
new DiscoverySystemBuilder()
.signer(new LocalNodeKeySigner(nodeKey))
.localNodeRecord(localNodeRecord)
.localNodeRecordListener((previous, updated) -> nodeRecordManager.updateNodeRecord())
.newAddressHandler((nodeRecord, newAddress) -> Optional.of(nodeRecord))
// TODO Integrate address filtering based on peer permissions
.addressAccessPolicy(AddressAccessPolicy.ALLOW_ALL);

if (disc.isDualStackEnabled()) {
final InetSocketAddress ipv4 = new InetSocketAddress(disc.getBindHost(), disc.getBindPort());
final InetSocketAddress ipv6 =
new InetSocketAddress(disc.getBindHostIpv6().orElseThrow(), disc.getBindPortIpv6());
builder.listen(ipv4, ipv6);
} else {
builder.listen(disc.getBindHost(), disc.getBindPort());
}

final MutableDiscoverySystem discoverySystem = builder.buildMutable();

return new PeerDiscoveryAgentV5(
discoverySystem,
nodeKey,
config,
forkIdManager,
nodeRecordManager,
rlpxAgent,
disc.isPreferIpv6Outbound());
}

/**
* Initializes the local node record using the {@link NodeRecordManager}.
*
* @return the initialized local {@link NodeRecord}
* @throws IllegalStateException if the local node record has not been initialized
*/
private NodeRecord initializeLocalNodeRecord() {
final DiscoveryConfiguration disc = config.discoveryConfiguration();
nodeRecordManager.initializeLocalNode(
new HostEndpoint(disc.getAdvertisedHost(), disc.getBindPort(), disc.getBindPort()),
disc.getAdvertisedHostIpv6()
.map(host -> new HostEndpoint(host, disc.getBindPortIpv6(), disc.getBindPortIpv6())));

return nodeRecordManager
.getLocalNode()
.flatMap(DiscoveryPeer::getNodeRecord)
.orElseThrow(() -> new IllegalStateException("Local node record not initialized"));
}

/**
* An implementation of the {@link org.ethereum.beacon.discovery.crypto.Signer} interface that
* uses a local {@link NodeKey} for signing and key agreement.
*/
private static class LocalNodeKeySigner implements org.ethereum.beacon.discovery.crypto.Signer {
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithmFactory.getInstance();

private final NodeKey nodeKey;

/**
* Creates a new LocalNodeKeySigner.
*
* @param nodeKey the node key to use for signing and key agreement
*/
public LocalNodeKeySigner(final NodeKey nodeKey) {
this.nodeKey = nodeKey;
}

/**
* Derives a shared secret using ECDH with the given peer public key.
*
* @param remotePubKey the destination peer's public key
* @return the derived shared secret
*/
@Override
public Bytes deriveECDHKeyAgreement(final Bytes remotePubKey) {
SECPPublicKey publicKey = signatureAlgorithm.createPublicKey(remotePubKey);
return nodeKey.calculateECDHKeyAgreement(publicKey);
}

/**
* Creates a signature of message `x`.
*
* @param messageHash message, hashed
* @return ECDSA signature with properties merged together: r || s
*/
@Override
public Bytes sign(final Bytes32 messageHash) {
Bytes signature = nodeKey.sign(messageHash).encodedBytes();
return signature.slice(0, 64);
}

/**
* Derives the compressed public key corresponding to the private key held by this module.
*
* @return the compressed public key
*/
@Override
public Bytes deriveCompressedPublicKeyFromPrivate() {
return Bytes.wrap(
signatureAlgorithm.publicKeyAsEcPoint(nodeKey.getPublicKey()).getEncoded(true));
}
config.discoveryConfiguration().isPreferIpv6Outbound());
}
}
Loading