Skip to content

Commit

Permalink
Feature/end to end discovery test (#8098)
Browse files Browse the repository at this point in the history
  • Loading branch information
asdacap authored Jan 24, 2025
1 parent 32ed172 commit 4cf0c47
Show file tree
Hide file tree
Showing 31 changed files with 543 additions and 104 deletions.
2 changes: 1 addition & 1 deletion src/Nethermind/Nethermind.Api/IApiWithStores.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public interface IApiWithStores : IBasicApi
ILogFinder? LogFinder { get; set; }
ISigner? EngineSigner { get; set; }
ISignerStore? EngineSignerStore { get; set; }
ProtectedPrivateKey? NodeKey { get; set; }
IProtectedPrivateKey? NodeKey { get; set; }
IReceiptStorage? ReceiptStorage { get; set; }
IReceiptFinder? ReceiptFinder { get; set; }
IReceiptMonitor? ReceiptMonitor { get; set; }
Expand Down
5 changes: 1 addition & 4 deletions src/Nethermind/Nethermind.Api/IBasicApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,12 @@
using Autofac;
using Nethermind.Abi;
using Nethermind.Api.Extensions;
using Nethermind.Blockchain.Receipts;
using Nethermind.Blockchain.Synchronization;
using Nethermind.Config;
using Nethermind.Core;
using Nethermind.Core.Specs;
using Nethermind.Core.Timers;
using Nethermind.Crypto;
using Nethermind.Db;
using Nethermind.Era1;
using Nethermind.KeyStore;
using Nethermind.Logging;
using Nethermind.Serialization.Json;
Expand All @@ -39,7 +36,7 @@ public interface IBasicApi
IFileSystem FileSystem { get; set; }
IKeyStore? KeyStore { get; set; }
ILogManager LogManager { get; set; }
ProtectedPrivateKey? OriginalSignerKey { get; set; }
IProtectedPrivateKey? OriginalSignerKey { get; set; }
IReadOnlyList<INethermindPlugin> Plugins { get; }
[SkipServiceCollection]
string SealEngineType { get; set; }
Expand Down
4 changes: 2 additions & 2 deletions src/Nethermind/Nethermind.Api/NethermindApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,12 +233,12 @@ public ISealEngine SealEngine
public IWebSocketsManager WebSocketsManager { get; set; } = new WebSocketsManager();

public ISubscriptionFactory? SubscriptionFactory { get; set; }
public ProtectedPrivateKey? NodeKey { get; set; }
public IProtectedPrivateKey? NodeKey { get; set; }

/// <summary>
/// Key used for signing blocks. Original as its loaded on startup. This can later be changed via RPC in <see cref="Signer"/>.
/// </summary>
public ProtectedPrivateKey? OriginalSignerKey { get; set; }
public IProtectedPrivateKey? OriginalSignerKey { get; set; }

public ChainSpec ChainSpec { get; set; }
public DisposableStack DisposeStack { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class RandomContractTxSource : ITxSource
{
private readonly IEciesCipher _eciesCipher;
private readonly ISigner _signer;
private readonly ProtectedPrivateKey _previousCryptoKey;
private readonly IProtectedPrivateKey _previousCryptoKey;
private readonly IList<IRandomContract> _contracts;
private readonly ICryptoRandom _random;
private readonly ILogger _logger;
Expand All @@ -36,7 +36,7 @@ public RandomContractTxSource(
IList<IRandomContract> contracts,
IEciesCipher eciesCipher,
ISigner signer,
ProtectedPrivateKey previousCryptoKey, // this is for backwards-compability when upgrading validator node
IProtectedPrivateKey previousCryptoKey, // this is for backwards-compability when upgrading validator node
ICryptoRandom cryptoRandom,
ILogManager logManager)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Nethermind/Nethermind.Consensus/ISignerStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ public interface ISignerStore
{
void SetSigner(PrivateKey key);

void SetSigner(ProtectedPrivateKey key);
void SetSigner(IProtectedPrivateKey key);
}
}
2 changes: 1 addition & 1 deletion src/Nethermind/Nethermind.Consensus/NullSigner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class NullSigner : ISigner, ISignerStore

public void SetSigner(PrivateKey key) { }

public void SetSigner(ProtectedPrivateKey key) { }
public void SetSigner(IProtectedPrivateKey key) { }

public Signature Sign(BlockHeader header) { return new(new byte[65]); }
}
Expand Down
4 changes: 2 additions & 2 deletions src/Nethermind/Nethermind.Consensus/Signer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public Signer(ulong chainId, PrivateKey? key, ILogManager logManager)
SetSigner(key);
}

public Signer(ulong chainId, ProtectedPrivateKey key, ILogManager logManager)
public Signer(ulong chainId, IProtectedPrivateKey key, ILogManager logManager)
{
_chainId = chainId;
_logger = logManager?.GetClassLogger<Signer>() ?? throw new ArgumentNullException(nameof(logManager));
Expand Down Expand Up @@ -66,7 +66,7 @@ public void SetSigner(PrivateKey? key)
_key is not null ? $"Address {Address} is configured for signing blocks." : "No address is configured for signing blocks.");
}

public void SetSigner(ProtectedPrivateKey? key)
public void SetSigner(IProtectedPrivateKey? key)
{
PrivateKey? pk = null;
if (key is not null)
Expand Down
93 changes: 93 additions & 0 deletions src/Nethermind/Nethermind.Core.Test/Modules/DiscoveryModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System.Linq;
using Autofac;
using Nethermind.Api;
using Nethermind.Crypto;
using Nethermind.Logging;
using Nethermind.Network;
using Nethermind.Network.Config;
using Nethermind.Network.Discovery;
using Nethermind.Network.Dns;
using Nethermind.Network.Enr;
using Nethermind.Network.StaticNodes;
using Nethermind.Specs.ChainSpecStyle;

namespace Nethermind.Core.Test.Modules;

public class DiscoveryModule(IInitConfig initConfig, INetworkConfig networkConfig) : Module
{
protected override void Load(ContainerBuilder builder)
{
builder
// Enr discovery uses DNS to get some bootnodes.
// TODO: Node source to discovery 4 feeder.
.AddSingleton<EnrDiscovery, IEthereumEcdsa, ILogManager>((ethereumEcdsa, logManager) =>
{
// I do not use the key here -> API is broken - no sense to use the node signer here
NodeRecordSigner nodeRecordSigner = new(ethereumEcdsa, new PrivateKeyGenerator().Generate());
EnrRecordParser enrRecordParser = new(nodeRecordSigner);
return new EnrDiscovery(enrRecordParser, networkConfig, logManager); // initialize with a proper network
})

// Uses by RPC also.
.AddSingleton<IStaticNodesManager, ILogManager>((logManager) => new StaticNodesManager(initConfig.StaticNodesPath, logManager))
// This load from file.
.AddSingleton<NodesLoader>()

.Bind<INodeSource, IStaticNodesManager>()
.Bind<INodeSource, NodesLoader>()
.AddComposite<INodeSource, CompositeNodeSource>()

// The actual thing that uses the INodeSource(s)
.AddSingleton<IPeerPool, PeerPool>()
.AddSingleton<IPeerManager, PeerManager>()

// Some config migration
.AddDecorator<INetworkConfig>((ctx, networkConfig) =>
{
ChainSpec chainSpec = ctx.Resolve<ChainSpec>();
IDiscoveryConfig discoveryConfig = ctx.Resolve<IDiscoveryConfig>();

// Was in `UpdateDiscoveryConfig` step.
if (discoveryConfig.Bootnodes != string.Empty)
{
if (chainSpec.Bootnodes.Length != 0)
{
discoveryConfig.Bootnodes += "," + string.Join(",", chainSpec.Bootnodes.Select(static bn => bn.ToString()));
}
}
else if (chainSpec.Bootnodes is not null)
{
discoveryConfig.Bootnodes = string.Join(",", chainSpec.Bootnodes.Select(static bn => bn.ToString()));
}

if (networkConfig.DiscoveryDns == null)
{
string chainName = BlockchainIds.GetBlockchainName(chainSpec!.NetworkId).ToLowerInvariant();
networkConfig.DiscoveryDns = $"all.{chainName}.ethdisco.net";
}
networkConfig.Bootnodes = discoveryConfig.Bootnodes;
return networkConfig;
})
;


// Discovery app
// Needed regardless if used or not because of StopAsync in runner.
// The DiscV4/5 is in CompositeDiscoveryApp.
if (!initConfig.DiscoveryEnabled)
builder.AddSingleton<IDiscoveryApp, NullDiscoveryApp>();
else
builder.AddSingleton<IDiscoveryApp, CompositeDiscoveryApp>();

if (!networkConfig.OnlyStaticPeers)
{
// These are INodeSource only if `OnlyStaticPeers` is false.
builder
.Bind<INodeSource, IDiscoveryApp>()
.Bind<INodeSource, EnrDiscovery>();
}
}
}
19 changes: 19 additions & 0 deletions src/Nethermind/Nethermind.Core.Test/Modules/FixedIpResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System.Net;
using System.Threading.Tasks;
using Nethermind.Network;
using Nethermind.Network.Config;

namespace Nethermind.Core.Test.Modules;

public class FixedIpResolver(INetworkConfig networkConfig) : IIPResolver
{
public IPAddress LocalIp => IPAddress.Parse(networkConfig.LocalIp!);
public IPAddress ExternalIp => IPAddress.Parse(networkConfig.ExternalIp!);
public Task Initialize()
{
return Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using Nethermind.Core.Crypto;
using Nethermind.Crypto;

namespace Nethermind.Core.Test.Modules;

public class InsecureProtectedPrivateKey(PrivateKey privateKey) : IProtectedPrivateKey
{
public PublicKey PublicKey => privateKey.PublicKey;
public CompressedPublicKey CompressedPublicKey => privateKey.CompressedPublicKey;
public PrivateKey Unprotect()
{
return privateKey;
}
}
133 changes: 133 additions & 0 deletions src/Nethermind/Nethermind.Core.Test/Modules/LocalChannelFactory.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Net;
using System.Net.NetworkInformation;
using System.Threading.Tasks;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Embedded;
using DotNetty.Transport.Channels.Local;
using DotNetty.Transport.Channels.Sockets;
using Nethermind.Network;
using Nethermind.Network.Config;
using NonBlocking;
using TaskCompletionSource = DotNetty.Common.Concurrency.TaskCompletionSource;

namespace Nethermind.Core.Test.Modules;

Expand All @@ -32,6 +39,11 @@ public IChannel CreateClient()
return new LocalClientChannel(networkGroup, LocalEndpoint);
}

public IChannel CreateDatagramChannel()
{
return new LocalDatagramChannel(networkGroup);
}

private class LocalClientChannel(string networkGroup, IPEndPoint localIPEndpoint) : LocalChannel
{
public override Task ConnectAsync(EndPoint remoteAddress, EndPoint localAddress)
Expand Down Expand Up @@ -97,4 +109,125 @@ public override int GetHashCode()
return Id.GetHashCode();
}
}

private class LocalDatagramChannel(string networkGroup) : EmbeddedChannel(EmbeddedChannelId.Instance, false, false, []), IDatagramChannel
{
private static ConcurrentDictionary<(string, EndPoint), WeakReference<LocalDatagramChannel>> channelRegistry = new();

private EndPoint? _bondedEndpoint;

protected override bool IsCompatible(IEventLoop eventLoop)
{
// Not sure why its only compatible with EmbeddedEventLoop originally..
return true;
}

protected override void DoBind(EndPoint localAddress)
{
channelRegistry.TryAdd((networkGroup, localAddress), new WeakReference<LocalDatagramChannel>(this));
_bondedEndpoint = localAddress;
base.DoBind(localAddress);
}

public override async Task CloseAsync()
{
channelRegistry.TryRemove((networkGroup, _bondedEndpoint!), out _);
await base.CloseAsync();
}

protected override void DoWrite(ChannelOutboundBuffer input)
{
for (; ; )
{
object msg = input.Current;
if (msg == null)
{
break;
}

if (msg is DatagramPacket addressedEnvelope)
{
if (channelRegistry.TryGetValue((networkGroup, addressedEnvelope.Recipient), out WeakReference<LocalDatagramChannel>? reference) && reference.TryGetTarget(out LocalDatagramChannel? recipient))
{
DatagramPacket newEnvelop = new DatagramPacket(addressedEnvelope.Content, _bondedEndpoint, addressedEnvelope.Recipient);
ReferenceCountUtil.Retain(newEnvelop);
recipient!.WriteInbound(newEnvelop);
}
}
else
{
throw new InvalidOperationException($"Unsupported message type {msg.GetType()}");
}

input.Remove();
}
}

public bool IsConnected()
{
return true;
}

public Task JoinGroup(IPEndPoint multicastAddress)
{
return Task.CompletedTask;
}

public Task JoinGroup(IPEndPoint multicastAddress, TaskCompletionSource promise)
{
return Task.CompletedTask;
}

public Task JoinGroup(IPEndPoint multicastAddress, NetworkInterface networkInterface)
{
return Task.CompletedTask;
}

public Task JoinGroup(IPEndPoint multicastAddress, NetworkInterface networkInterface, TaskCompletionSource promise)
{
return Task.CompletedTask;
}

public Task JoinGroup(IPEndPoint multicastAddress, NetworkInterface networkInterface, IPEndPoint source)
{
return Task.CompletedTask;
}

public Task JoinGroup(IPEndPoint multicastAddress, NetworkInterface networkInterface, IPEndPoint source,
TaskCompletionSource promise)
{
return Task.CompletedTask;
}

public Task LeaveGroup(IPEndPoint multicastAddress)
{
return Task.CompletedTask;
}

public Task LeaveGroup(IPEndPoint multicastAddress, TaskCompletionSource promise)
{
return Task.CompletedTask;
}

public Task LeaveGroup(IPEndPoint multicastAddress, NetworkInterface networkInterface)
{
return Task.CompletedTask;
}

public Task LeaveGroup(IPEndPoint multicastAddress, NetworkInterface networkInterface, TaskCompletionSource promise)
{
return Task.CompletedTask;
}

public Task LeaveGroup(IPEndPoint multicastAddress, NetworkInterface networkInterface, IPEndPoint source)
{
return Task.CompletedTask;
}

public Task LeaveGroup(IPEndPoint multicastAddress, NetworkInterface networkInterface, IPEndPoint source,
TaskCompletionSource promise)
{
return Task.CompletedTask;
}
}
}
Loading

0 comments on commit 4cf0c47

Please sign in to comment.