Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
151 changes: 33 additions & 118 deletions src/Nethermind/Chains/xdc.json

Large diffs are not rendered by default.

16 changes: 9 additions & 7 deletions src/Nethermind/Nethermind.Network.Discovery/DiscoveryApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,20 @@ namespace Nethermind.Network.Discovery;
public class DiscoveryApp : IDiscoveryApp
{
private readonly IDiscoveryConfig _discoveryConfig;
private readonly ITimestamper _timestamper;
protected readonly ITimestamper _timestamper;
private readonly INodesLocator _nodesLocator;
private readonly IDiscoveryManager _discoveryManager;
protected readonly IDiscoveryManager _discoveryManager;
private readonly INodeTable _nodeTable;
private readonly ILogManager _logManager;
protected readonly ILogManager _logManager;
private readonly ILogger _logger;
private readonly IMessageSerializationService _messageSerializationService;
protected readonly IMessageSerializationService _messageSerializationService;
private readonly ICryptoRandom _cryptoRandom;
private readonly INetworkStorage _discoveryStorage;
private readonly DiscoveryPersistenceManager _persistenceManager;
private readonly IProcessExitSource _processExitSource;
private readonly INetworkConfig _networkConfig;
private readonly CancellationTokenSource _stopCts;
private readonly NodeFilter? _inboundMessageFilter;
protected readonly NodeFilter? _inboundMessageFilter;

private NettyDiscoveryHandler? _discoveryHandler;
private Task? _runningTask;
Expand Down Expand Up @@ -178,10 +178,12 @@ private void ResetUnreachableStatus(object? sender, NetworkAvailabilityEventArgs
}
}

protected virtual NettyDiscoveryHandler CreateDiscoveryHandler(IChannel channel) =>
new(_discoveryManager, channel, _messageSerializationService, _timestamper, _logManager, _inboundMessageFilter);

public void InitializeChannel(IChannel channel)
{
_discoveryHandler = new NettyDiscoveryHandler(_discoveryManager, channel, _messageSerializationService,
_timestamper, _logManager, _inboundMessageFilter);
_discoveryHandler = CreateDiscoveryHandler(channel);
_discoveryManager.MsgSender = _discoveryHandler;
_discoveryHandler.OnChannelActivated += OnChannelActivated;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public void ProcessPingMsg(PingMsg pingMsg)
RefreshNodeContactTime();
}

private void SendEnrRequest()
protected virtual void SendEnrRequest()
{
EnrRequestMsg msg = new(ManagedNode.Address, CalculateExpirationTime());
_discoveryManager.SendMessage(msg);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,11 @@ private bool TryParseMessage(DatagramPacket packet, out DiscoveryMsg? msg, out b
return false;
}

byte typeRaw = msgBytes[97];
if (!FastEnum.IsDefined((MsgType)typeRaw))
if (FromMsgTypeByte(msgBytes[97]) is not { } type)
{
if (_logger.IsDebug) _logger.Debug($"Unsupported message type: {typeRaw}, sender: {address}, message {msgBytes.AsSpan(0, size).ToHexString()}");
if (_logger.IsDebug) _logger.Debug($"Unsupported message type: {msgBytes[97]}, sender: {address}, message {msgBytes.AsSpan(0, size).ToHexString()}");
return false;
}

MsgType type = (MsgType)typeRaw;
if (_logger.IsTrace) _logger.Trace($"Received message: {type}");

if (address is IPEndPoint remoteEndpoint && !TryAcceptInbound(remoteEndpoint))
Expand Down Expand Up @@ -194,6 +191,9 @@ protected override void ChannelRead0(IChannelHandlerContext ctx, DatagramPacket
}
}

protected virtual MsgType? FromMsgTypeByte(byte b) =>
FastEnum.IsDefined((MsgType)b) ? (MsgType)b : null;

private DiscoveryMsg Deserialize(MsgType type, ArraySegment<byte> msg) => type switch
{
MsgType.Ping => _msgSerializationService.Deserialize<PingMsg>(msg),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ namespace Nethermind.Network.Discovery.Serializers;

public class PingMsgSerializer(IEcdsa ecdsa, [KeyFilter(IProtectedPrivateKey.NodeKey)] IPrivateKeyGenerator nodeKey, INodeIdResolver nodeIdResolver) : DiscoveryMsgSerializerBase(ecdsa, nodeKey, nodeIdResolver), IZeroInnerMessageSerializer<PingMsg>
{
protected virtual byte MsgTypeByte => (byte)MsgType.Ping;

public void Serialize(IByteBuffer byteBuffer, PingMsg msg)
{
(int totalLength, int contentLength, int sourceAddressLength, int destinationAddressLength) = GetLength(msg);

byteBuffer.MarkIndex();
PrepareBufferForSerialization(byteBuffer, totalLength, (byte)msg.MsgType);
PrepareBufferForSerialization(byteBuffer, totalLength, MsgTypeByte);
NettyRlpStream stream = new(byteBuffer);
stream.StartSequence(contentLength);
stream.Encode(msg.Version);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public MessageSerializationService(params IReadOnlyList<SerializerInfo> serializ
ThrowInvalidSerializer(Serializer, expectedInterface);
}

_zeroSerializers.TryAdd(MessageType.TypeHandle, Serializer);
_zeroSerializers[MessageType.TypeHandle] = Serializer;
}

[DoesNotReturn, StackTraceHidden]
Expand Down
7 changes: 7 additions & 0 deletions src/Nethermind/Nethermind.Runner/configs/xdc-testnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
"BaseDbPath": "nethermind_db/xdc-testnet",
"LogFileName": "xdc-testnet.log"
},
"Network": {
"FilterDiscoveryNodesByRecentIp": false,
"EnableEnrDiscovery": false
},
"Discovery": {
"DiscoveryVersion": "V4"
},
"TxPool": {
"BlobsSupport": "Disabled"
},
Expand Down
7 changes: 7 additions & 0 deletions src/Nethermind/Nethermind.Runner/configs/xdc.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
"BaseDbPath": "nethermind_db/xdc",
"LogFileName": "xdc.log"
},
"Network": {
"FilterDiscoveryNodesByRecentIp": false,
"EnableEnrDiscovery": false
},
"Discovery": {
"DiscoveryVersion": "V4"
},
"TxPool": {
"BlobsSupport": "Disabled"
},
Expand Down
3 changes: 2 additions & 1 deletion src/Nethermind/Nethermind.Runner/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -1252,7 +1252,8 @@
"Nethermind.Api": "[1.38.0-unstable, )",
"Nethermind.Consensus": "[1.38.0-unstable, )",
"Nethermind.Core": "[1.38.0-unstable, )",
"Nethermind.Init": "[1.38.0-unstable, )"
"Nethermind.Init": "[1.38.0-unstable, )",
"Nethermind.Network.Discovery": "[1.38.0-unstable, )"
}
},
"AspNetCore.HealthChecks.UI": {
Expand Down
63 changes: 63 additions & 0 deletions src/Nethermind/Nethermind.Xdc.Test/Discovery/XdcDiscoveryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System.Net;
using DotNetty.Buffers;
using Nethermind.Core;
using Nethermind.Core.Test.Builders;
using Nethermind.Crypto;
using Nethermind.Network;
using Nethermind.Network.Discovery;
using Nethermind.Network.Discovery.Messages;
using Nethermind.Network.Test;
using Nethermind.Xdc.Discovery;
using NSubstitute;
using NUnit.Framework;

namespace Nethermind.Xdc.Test.Discovery;

[TestFixture, Parallelizable(ParallelScope.All)]
public class XdcDiscoveryTests
{
[Test]
public void XdcPingMsgSerializer_WritesXdcTypeByte()
{
// Packet layout: 32-byte MDC + 64-byte sig + 1-byte recovery id + 1-byte type = type at index 97
Ecdsa ecdsa = new();
XdcPingMsgSerializer serializer = new(ecdsa, new SameKeyGenerator(TestItem.PrivateKeyA), new NodeIdResolver(ecdsa));
PingMsg msg = new(
TestItem.PublicKeyA,
Timestamper.Default.UnixTime.SecondsLong + 1200,
new(IPAddress.Loopback, 30303),
new(IPAddress.Loopback, 30304),
new byte[32]
);

using DisposableByteBuffer buffer = Unpooled.Buffer().AsDisposable();
serializer.Serialize(buffer, msg);
Assert.That(buffer.GetByte(97), Is.EqualTo((byte)5));
}

private sealed class ExposedXdcNettyDiscoveryHandler : XdcNettyDiscoveryHandler
{
public ExposedXdcNettyDiscoveryHandler()
: base(
Substitute.For<IDiscoveryMsgListener>(),
Substitute.For<DotNetty.Transport.Channels.IChannel>(),
Substitute.For<IMessageSerializationService>(),
Timestamper.Default,
Nethermind.Logging.LimboLogs.Instance)
{
}

public MsgType? ExposedFromMsgTypeByte(byte b) => FromMsgTypeByte(b);
}

[TestCase((byte)5, MsgType.Ping)]
[TestCase((byte)1, null)]
public void XdcNettyDiscoveryHandler_FromMsgTypeByte(byte input, MsgType? expected)
{
ExposedXdcNettyDiscoveryHandler handler = new();
Assert.That(handler.ExposedFromMsgTypeByte(input), Is.EqualTo(expected));
}
}
40 changes: 40 additions & 0 deletions src/Nethermind/Nethermind.Xdc/Discovery/XdcDiscoveryApp.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using Autofac.Features.AttributeFilters;
using DotNetty.Transport.Channels;
using Nethermind.Config;
using Nethermind.Core;
using Nethermind.Crypto;
using Nethermind.Db;
using Nethermind.Logging;
using Nethermind.Network;
using Nethermind.Network.Config;
using Nethermind.Network.Discovery;
using Nethermind.Network.Discovery.RoutingTable;

namespace Nethermind.Xdc.Discovery;

// Parameters mirror DiscoveryApp so Autofac resolves [KeyFilter] attributes via WithAttributeFiltering().
public class XdcDiscoveryApp(
Comment thread
batrr marked this conversation as resolved.
[KeyFilter(IProtectedPrivateKey.NodeKey)] IProtectedPrivateKey nodeKey,
INodesLocator nodesLocator,
IDiscoveryManager? discoveryManager,
INodeTable? nodeTable,
IMessageSerializationService? msgSerializationService,
ICryptoRandom? cryptoRandom,
[KeyFilter(DbNames.DiscoveryNodes)] INetworkStorage? discoveryStorage,
DiscoveryPersistenceManager discoveryPersistenceManager,
IProcessExitSource processExitSource,
INetworkConfig? networkConfig,
IDiscoveryConfig? discoveryConfig,
ITimestamper? timestamper,
ILogManager? logManager,
NodeFilter? inboundMessageFilter = null)
: DiscoveryApp(nodeKey, nodesLocator, discoveryManager, nodeTable, msgSerializationService, cryptoRandom,
discoveryStorage, discoveryPersistenceManager, processExitSource, networkConfig, discoveryConfig,
timestamper, logManager, inboundMessageFilter)
{
protected override NettyDiscoveryHandler CreateDiscoveryHandler(IChannel channel) =>
new XdcNettyDiscoveryHandler(_discoveryManager, channel, _messageSerializationService, _timestamper, _logManager, _inboundMessageFilter);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using DotNetty.Transport.Channels;
using Nethermind.Core;
using Nethermind.Logging;
using Nethermind.Network;
using Nethermind.Network.Discovery;
using Nethermind.Network.Discovery.Messages;

namespace Nethermind.Xdc.Discovery;

public class XdcNettyDiscoveryHandler(
IDiscoveryMsgListener? discoveryManager,
IChannel? channel,
IMessageSerializationService? msgSerializationService,
ITimestamper? timestamper,
ILogManager? logManager,
NodeFilter? inboundMessageFilter = null)
: NettyDiscoveryHandler(discoveryManager, channel, msgSerializationService, timestamper, logManager, inboundMessageFilter)
{
// XDC remapped the standard disc-v4 type bytes: byte 1 (standard Ping) is unused;
// byte 5 is XDC's pingXDC. ENR (bytes 5/6 in standard geth) is not supported by XDC
protected override MsgType? FromMsgTypeByte(byte b) => b switch
Comment thread
batrr marked this conversation as resolved.
{
2 => MsgType.Pong,
3 => MsgType.FindNode,
4 => MsgType.Neighbors,
5 => MsgType.Ping,
_ => null
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using Nethermind.Core;
using Nethermind.Logging;
using Nethermind.Network.Discovery;
using Nethermind.Network.Discovery.Lifecycle;
using Nethermind.Network.Discovery.RoutingTable;
using Nethermind.Network.Enr;
using Nethermind.Stats;
using Nethermind.Stats.Model;

namespace Nethermind.Xdc.Discovery;

/// <summary>XDC-aware lifecycle manager that suppresses ENR requests.</summary>
/// <remarks>
/// XDC does not support ENR (disc-v4 bytes 5/6). XDC remaps byte 5 to its own pingXDC type,
/// so a standard ENR request would be misread as a Ping by XDC peers.
/// </remarks>
public class XdcNodeLifecycleManager(
Node node,
IDiscoveryManager discoveryManager,
INodeTable nodeTable,
IEvictionManager evictionManager,
INodeStats nodeStats,
NodeRecord nodeRecord,
IDiscoveryConfig discoveryConfig,
ITimestamper timestamper,
ILogger logger)
: NodeLifecycleManager(node, discoveryManager, nodeTable, evictionManager, nodeStats, nodeRecord, discoveryConfig, timestamper, logger)
{
protected override void SendEnrRequest() { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using Nethermind.Core;
using Nethermind.Logging;
using Nethermind.Network.Discovery;
using Nethermind.Network.Discovery.Lifecycle;
using Nethermind.Network.Discovery.RoutingTable;
using Nethermind.Network.Enr;
using Nethermind.Stats;
using Nethermind.Stats.Model;

namespace Nethermind.Xdc.Discovery;

public class XdcNodeLifecycleManagerFactory(
INodeTable nodeTable,
IEvictionManager evictionManager,
INodeStatsManager nodeStatsManager,
INodeRecordProvider nodeRecordProvider,
IDiscoveryConfig discoveryConfig,
ITimestamper timestamper,
ILogManager? logManager) : INodeLifecycleManagerFactory
{
private readonly INodeTable _nodeTable = nodeTable ?? throw new ArgumentNullException(nameof(nodeTable));
private readonly ILogger _logger = logManager?.GetClassLogger<XdcNodeLifecycleManagerFactory>() ?? throw new ArgumentNullException(nameof(logManager));
private readonly IDiscoveryConfig _discoveryConfig = discoveryConfig ?? throw new ArgumentNullException(nameof(discoveryConfig));
private readonly ITimestamper _timestamper = timestamper ?? throw new ArgumentNullException(nameof(timestamper));
private readonly IEvictionManager _evictionManager = evictionManager ?? throw new ArgumentNullException(nameof(evictionManager));
private readonly INodeStatsManager _nodeStatsManager = nodeStatsManager ?? throw new ArgumentNullException(nameof(nodeStatsManager));
private readonly NodeRecord _selfNodeRecord = (nodeRecordProvider ?? throw new ArgumentNullException(nameof(nodeRecordProvider))).Current;

public IDiscoveryManager? DiscoveryManager { private get; set; }
public NodeRecord SelfNodeRecord => _selfNodeRecord;

public INodeLifecycleManager CreateNodeLifecycleManager(Node node)
{
if (DiscoveryManager is null)
throw new Exception($"{nameof(DiscoveryManager)} has to be set");

return new XdcNodeLifecycleManager(
node,
DiscoveryManager,
_nodeTable,
_evictionManager,
_nodeStatsManager.GetOrAdd(node),
_selfNodeRecord,
_discoveryConfig,
_timestamper,
_logger);
}
}
15 changes: 15 additions & 0 deletions src/Nethermind/Nethermind.Xdc/Discovery/XdcPingMsgSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using Autofac.Features.AttributeFilters;
using Nethermind.Crypto;
using Nethermind.Network.Discovery.Messages;
using Nethermind.Network.Discovery.Serializers;

namespace Nethermind.Xdc.Discovery;

public class XdcPingMsgSerializer(IEcdsa ecdsa, [KeyFilter(IProtectedPrivateKey.NodeKey)] IPrivateKeyGenerator nodeKey, INodeIdResolver nodeIdResolver)
: PingMsgSerializer(ecdsa, nodeKey, nodeIdResolver)
{
protected override byte MsgTypeByte => 5;
}
1 change: 1 addition & 0 deletions src/Nethermind/Nethermind.Xdc/Nethermind.Xdc.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<ProjectReference Include="..\Nethermind.Consensus\Nethermind.Consensus.csproj" />
<ProjectReference Include="..\Nethermind.Core\Nethermind.Core.csproj" />
<ProjectReference Include="..\Nethermind.Init\Nethermind.Init.csproj" />
<ProjectReference Include="..\Nethermind.Network.Discovery\Nethermind.Network.Discovery.csproj" />
</ItemGroup>

</Project>
Loading
Loading