Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/end to end discovery test #8098

Merged
merged 8 commits into from
Jan 24, 2025
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
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NotProtetedPriveteKey :D

{
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
Loading