From 4cf0c475a1000843e721b6cbdb6a29ed435f5f01 Mon Sep 17 00:00:00 2001 From: Amirul Ashraf Date: Fri, 24 Jan 2025 18:40:45 +0800 Subject: [PATCH] Feature/end to end discovery test (#8098) --- .../Nethermind.Api/IApiWithStores.cs | 2 +- src/Nethermind/Nethermind.Api/IBasicApi.cs | 5 +- .../Nethermind.Api/NethermindApi.cs | 4 +- .../Transactions/RandomContractTxSource.cs | 4 +- .../Nethermind.Consensus/ISignerStore.cs | 2 +- .../Nethermind.Consensus/NullSigner.cs | 2 +- src/Nethermind/Nethermind.Consensus/Signer.cs | 4 +- .../Modules/DiscoveryModule.cs | 93 ++++++++++++ .../Modules/FixedIpResolver.cs | 19 +++ .../Modules/InsecureProtectedPrivateKey.cs | 17 +++ .../Modules/LocalChannelFactory.cs | 133 ++++++++++++++++++ .../Modules/NetworkModule.cs | 27 +++- .../Modules/PseudoNethermindModule.cs | 11 +- .../Modules/PseudoNethermindRunner.cs | 41 +++++- .../Modules/TestEnvironmentModule.cs | 24 ++-- .../Nethermind.Crypto/IProtectedPrivateKey.cs | 15 ++ .../Nethermind.Crypto/ProtectedPrivateKey.cs | 2 +- .../ClefSigner.cs | 2 +- .../Steps/InitializeNetwork.cs | 4 +- .../Nethermind.Init/Steps/SetupKeyStore.cs | 2 +- .../Config/INetworkConfig.cs | 3 + .../E2EDiscoveryTests.cs | 95 +++++++++++++ .../CompositeDiscoveryApp.cs | 25 ++-- .../Nethermind.Network.Dns/EnrDiscovery.cs | 3 + .../Rlpx/RlpxPeerTests.cs | 19 ++- .../Config/NetworkConfig.cs | 1 + .../Nethermind.Network/IChannelFactory.cs | 6 +- .../Nethermind.Network/NodesLoader.cs | 3 +- src/Nethermind/Nethermind.Network/PeerPool.cs | 3 +- .../Rlpx/Handshake/HandshakeService.cs | 11 ++ .../Nethermind.Network/Rlpx/RlpxHost.cs | 65 +++------ 31 files changed, 543 insertions(+), 104 deletions(-) create mode 100644 src/Nethermind/Nethermind.Core.Test/Modules/DiscoveryModule.cs create mode 100644 src/Nethermind/Nethermind.Core.Test/Modules/FixedIpResolver.cs create mode 100644 src/Nethermind/Nethermind.Core.Test/Modules/InsecureProtectedPrivateKey.cs create mode 100644 src/Nethermind/Nethermind.Crypto/IProtectedPrivateKey.cs create mode 100644 src/Nethermind/Nethermind.Network.Discovery.Test/E2EDiscoveryTests.cs diff --git a/src/Nethermind/Nethermind.Api/IApiWithStores.cs b/src/Nethermind/Nethermind.Api/IApiWithStores.cs index 459b92e3339..b29312b8847 100644 --- a/src/Nethermind/Nethermind.Api/IApiWithStores.cs +++ b/src/Nethermind/Nethermind.Api/IApiWithStores.cs @@ -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; } diff --git a/src/Nethermind/Nethermind.Api/IBasicApi.cs b/src/Nethermind/Nethermind.Api/IBasicApi.cs index 17195c30214..b705f43b451 100644 --- a/src/Nethermind/Nethermind.Api/IBasicApi.cs +++ b/src/Nethermind/Nethermind.Api/IBasicApi.cs @@ -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; @@ -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 Plugins { get; } [SkipServiceCollection] string SealEngineType { get; set; } diff --git a/src/Nethermind/Nethermind.Api/NethermindApi.cs b/src/Nethermind/Nethermind.Api/NethermindApi.cs index 8cae082a77e..76b15df071f 100644 --- a/src/Nethermind/Nethermind.Api/NethermindApi.cs +++ b/src/Nethermind/Nethermind.Api/NethermindApi.cs @@ -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; } /// /// Key used for signing blocks. Original as its loaded on startup. This can later be changed via RPC in . /// - public ProtectedPrivateKey? OriginalSignerKey { get; set; } + public IProtectedPrivateKey? OriginalSignerKey { get; set; } public ChainSpec ChainSpec { get; set; } public DisposableStack DisposeStack { get; } diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/Transactions/RandomContractTxSource.cs b/src/Nethermind/Nethermind.Consensus.AuRa/Transactions/RandomContractTxSource.cs index e60254ac475..9c2790b11bf 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/Transactions/RandomContractTxSource.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/Transactions/RandomContractTxSource.cs @@ -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 _contracts; private readonly ICryptoRandom _random; private readonly ILogger _logger; @@ -36,7 +36,7 @@ public RandomContractTxSource( IList 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) { diff --git a/src/Nethermind/Nethermind.Consensus/ISignerStore.cs b/src/Nethermind/Nethermind.Consensus/ISignerStore.cs index 95deb9cc17a..3704484edfc 100644 --- a/src/Nethermind/Nethermind.Consensus/ISignerStore.cs +++ b/src/Nethermind/Nethermind.Consensus/ISignerStore.cs @@ -9,6 +9,6 @@ public interface ISignerStore { void SetSigner(PrivateKey key); - void SetSigner(ProtectedPrivateKey key); + void SetSigner(IProtectedPrivateKey key); } } diff --git a/src/Nethermind/Nethermind.Consensus/NullSigner.cs b/src/Nethermind/Nethermind.Consensus/NullSigner.cs index f4976cac687..2f1a213d4e6 100644 --- a/src/Nethermind/Nethermind.Consensus/NullSigner.cs +++ b/src/Nethermind/Nethermind.Consensus/NullSigner.cs @@ -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]); } } diff --git a/src/Nethermind/Nethermind.Consensus/Signer.cs b/src/Nethermind/Nethermind.Consensus/Signer.cs index 0a22c939499..a5de6e77aec 100644 --- a/src/Nethermind/Nethermind.Consensus/Signer.cs +++ b/src/Nethermind/Nethermind.Consensus/Signer.cs @@ -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() ?? throw new ArgumentNullException(nameof(logManager)); @@ -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) diff --git a/src/Nethermind/Nethermind.Core.Test/Modules/DiscoveryModule.cs b/src/Nethermind/Nethermind.Core.Test/Modules/DiscoveryModule.cs new file mode 100644 index 00000000000..b7525779bdd --- /dev/null +++ b/src/Nethermind/Nethermind.Core.Test/Modules/DiscoveryModule.cs @@ -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((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((logManager) => new StaticNodesManager(initConfig.StaticNodesPath, logManager)) + // This load from file. + .AddSingleton() + + .Bind() + .Bind() + .AddComposite() + + // The actual thing that uses the INodeSource(s) + .AddSingleton() + .AddSingleton() + + // Some config migration + .AddDecorator((ctx, networkConfig) => + { + ChainSpec chainSpec = ctx.Resolve(); + IDiscoveryConfig discoveryConfig = ctx.Resolve(); + + // 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(); + else + builder.AddSingleton(); + + if (!networkConfig.OnlyStaticPeers) + { + // These are INodeSource only if `OnlyStaticPeers` is false. + builder + .Bind() + .Bind(); + } + } +} diff --git a/src/Nethermind/Nethermind.Core.Test/Modules/FixedIpResolver.cs b/src/Nethermind/Nethermind.Core.Test/Modules/FixedIpResolver.cs new file mode 100644 index 00000000000..66babdae0bb --- /dev/null +++ b/src/Nethermind/Nethermind.Core.Test/Modules/FixedIpResolver.cs @@ -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; + } +} diff --git a/src/Nethermind/Nethermind.Core.Test/Modules/InsecureProtectedPrivateKey.cs b/src/Nethermind/Nethermind.Core.Test/Modules/InsecureProtectedPrivateKey.cs new file mode 100644 index 00000000000..3cc70d45999 --- /dev/null +++ b/src/Nethermind/Nethermind.Core.Test/Modules/InsecureProtectedPrivateKey.cs @@ -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; + } +} diff --git a/src/Nethermind/Nethermind.Core.Test/Modules/LocalChannelFactory.cs b/src/Nethermind/Nethermind.Core.Test/Modules/LocalChannelFactory.cs index bd9994fa383..296da0596af 100644 --- a/src/Nethermind/Nethermind.Core.Test/Modules/LocalChannelFactory.cs +++ b/src/Nethermind/Nethermind.Core.Test/Modules/LocalChannelFactory.cs @@ -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; @@ -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) @@ -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> 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(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? 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; + } + } } diff --git a/src/Nethermind/Nethermind.Core.Test/Modules/NetworkModule.cs b/src/Nethermind/Nethermind.Core.Test/Modules/NetworkModule.cs index 9119fa28665..07c6d030127 100644 --- a/src/Nethermind/Nethermind.Core.Test/Modules/NetworkModule.cs +++ b/src/Nethermind/Nethermind.Core.Test/Modules/NetworkModule.cs @@ -12,6 +12,7 @@ using Nethermind.Init.Steps; using Nethermind.Logging; using Nethermind.Network; +using Nethermind.Network.Config; using Nethermind.Network.Contract.P2P; using Nethermind.Network.P2P.Analyzers; using Nethermind.Network.P2P.Messages; @@ -25,10 +26,11 @@ using Nethermind.Stats.Model; using Nethermind.Synchronization; using Nethermind.Synchronization.ParallelSync; +using Nethermind.TxPool; namespace Nethermind.Core.Test.Modules; -public class NetworkModule : Module +public class NetworkModule(IInitConfig initConfig) : Module { protected override void Load(ContainerBuilder builder) { @@ -39,6 +41,7 @@ protected override void Load(ContainerBuilder builder) .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton(No.BeaconSync) .AddSingleton() @@ -66,11 +69,13 @@ protected override void Load(ContainerBuilder builder) .AddSingleton() .AddSingleton() .AddSingleton(Policy.FullGossip) + .AddComposite() + + // TODO: LastNStateRootTracker .AddSingleton(stateProvider => stateProvider.SnapServer!) .AddKeyedSingleton(INetworkStorage.PeerDb, (ctx) => { - IInitConfig initConfig = ctx.Resolve(); ILogManager logManager = ctx.Resolve(); string dbName = INetworkStorage.PeerDb; @@ -106,8 +111,26 @@ protected override void Load(ContainerBuilder builder) }); }) + // Some config migration + .AddDecorator((ctx, networkConfig) => + { + ILogManager logManager = ctx.Resolve(); + if (networkConfig.DiagTracerEnabled) + { + NetworkDiagTracer.IsEnabled = true; + } + if (NetworkDiagTracer.IsEnabled) + { + NetworkDiagTracer.Start(logManager); + } + int maxPeersCount = networkConfig.ActivePeersMaxCount; + Network.Metrics.PeerLimit = maxPeersCount; + return networkConfig; + }) + ; // TODO: Add `WorldStateManager.InitializeNetwork`. } + } diff --git a/src/Nethermind/Nethermind.Core.Test/Modules/PseudoNethermindModule.cs b/src/Nethermind/Nethermind.Core.Test/Modules/PseudoNethermindModule.cs index 1fa2c953cdb..7f553ea863d 100644 --- a/src/Nethermind/Nethermind.Core.Test/Modules/PseudoNethermindModule.cs +++ b/src/Nethermind/Nethermind.Core.Test/Modules/PseudoNethermindModule.cs @@ -5,7 +5,6 @@ using System.Reflection; using Autofac; using Nethermind.Api; -using Nethermind.Blockchain.FullPruning; using Nethermind.Blockchain.Synchronization; using Nethermind.Config; using Nethermind.Consensus.Scheduler; @@ -13,12 +12,11 @@ using Nethermind.Core.Timers; using Nethermind.Crypto; using Nethermind.Db; -using Nethermind.Init; using Nethermind.Logging; using Nethermind.Network; +using Nethermind.Network.Config; using Nethermind.Serialization.Rlp; using Nethermind.Specs.ChainSpecStyle; -using Nethermind.State; using Module = Autofac.Module; namespace Nethermind.Core.Test.Modules; @@ -35,13 +33,16 @@ public class PseudoNethermindModule(ChainSpec spec, IConfigProvider configProvid protected override void Load(ContainerBuilder builder) { ISyncConfig syncConfig = configProvider.GetConfig(); + IInitConfig initConfig = configProvider.GetConfig(); + INetworkConfig networkConfig = configProvider.GetConfig(); base.Load(builder); builder .AddModule(new AppInputModule(spec, configProvider, logManager)) .AddModule(new SynchronizerModule(syncConfig)) - .AddModule(new NetworkModule()) + .AddModule(new NetworkModule(initConfig)) + .AddModule(new DiscoveryModule(initConfig, networkConfig)) .AddModule(new DbModule()) .AddModule(new WorldStateModule()) .AddModule(new BlockTreeModule()) @@ -51,7 +52,7 @@ protected override void Load(ContainerBuilder builder) // Environments .AddSingleton() .AddSingleton() - .AddSingleton((blockProcessingContext, initConfig) => new BackgroundTaskScheduler( + .AddSingleton((blockProcessingContext) => new BackgroundTaskScheduler( blockProcessingContext.BlockProcessor, initConfig.BackgroundTaskConcurrency, logManager)) diff --git a/src/Nethermind/Nethermind.Core.Test/Modules/PseudoNethermindRunner.cs b/src/Nethermind/Nethermind.Core.Test/Modules/PseudoNethermindRunner.cs index 33e9949a349..bf9c352933f 100644 --- a/src/Nethermind/Nethermind.Core.Test/Modules/PseudoNethermindRunner.cs +++ b/src/Nethermind/Nethermind.Core.Test/Modules/PseudoNethermindRunner.cs @@ -25,6 +25,10 @@ public class PseudoNethermindRunner(IComponentContext ctx) : IAsyncDisposable private IBlockchainProcessor? _blockchainProcessor; private IBlockProducerRunner? _blockProducerRunner; private IRlpxHost? _rlpxHost; + private ISessionMonitor? _sessionMonitor; + private IDiscoveryApp? _discoveryApp; + private IPeerPool? _peerPool; + private IPeerManager? _peerManager; public async Task StartBlockProcessing(CancellationToken cancellationToken) { @@ -67,18 +71,51 @@ public async Task StartNetwork(CancellationToken cancellationToken) // Protocol manager is what listen to rlpx for new connection which then send to sync peer pool. ctx.Resolve(); - _rlpxHost ??= ctx.Resolve(); - await _rlpxHost.Init(); + if (_rlpxHost is null) + { + _rlpxHost = ctx.Resolve(); + await _rlpxHost.Init(); + } // Sync peer pool has a loop that refresh the peer TD and header. ctx.Resolve().Start(); ctx.Resolve().Start(); } + public async Task StartDiscovery(CancellationToken cancellationToken) + { + // Needed by peer manager + if (_rlpxHost is null) + { + _rlpxHost = ctx.Resolve(); + await _rlpxHost.Init(); + } + + if (_sessionMonitor is not null) return; + await ctx.Resolve().InitAsync(); + + _discoveryApp = ctx.Resolve(); + _ = _discoveryApp.StartAsync(); // Bootstrap is not blocking by default + + _peerPool = ctx.Resolve(); + _peerPool.Start(); + + _peerManager = ctx.Resolve(); + _peerManager.Start(); + + _sessionMonitor = ctx.Resolve(); + _sessionMonitor.Start(); + } + public async ValueTask DisposeAsync() { await (_blockchainProcessor?.StopAsync() ?? Task.CompletedTask); await (_blockProducerRunner?.StopAsync() ?? Task.CompletedTask); await (_rlpxHost?.Shutdown() ?? Task.CompletedTask); + + _sessionMonitor?.Stop(); + await (_discoveryApp?.StopAsync() ?? Task.CompletedTask); + await (_peerPool?.StopAsync() ?? Task.CompletedTask); + await (_peerManager?.StopAsync() ?? Task.CompletedTask); } } diff --git a/src/Nethermind/Nethermind.Core.Test/Modules/TestEnvironmentModule.cs b/src/Nethermind/Nethermind.Core.Test/Modules/TestEnvironmentModule.cs index 786cada0acd..6626cbfbbb0 100644 --- a/src/Nethermind/Nethermind.Core.Test/Modules/TestEnvironmentModule.cs +++ b/src/Nethermind/Nethermind.Core.Test/Modules/TestEnvironmentModule.cs @@ -39,23 +39,18 @@ protected override void Load(ContainerBuilder builder) .AddSingleton(TestMemDbProvider.Init()) .AddSingleton(new InMemoryDictionaryFileStoreFactory()) .AddSingleton(networkConfig => new LocalChannelFactory(networkGroup ?? nameof(TestEnvironmentModule), networkConfig)) - .AddSingleton() .AddSingleton() .AddSingleton(new NethDevSealEngine(nodeKey.Address)) .AddSingleton() + .AddSingleton() + .AddKeyedSingleton(IProtectedPrivateKey.NodeKey, new InsecureProtectedPrivateKey(nodeKey)) .AddSingleton(networkConfig => { IPAddress ipAddress = networkConfig.ExternalIp is not null ? IPAddress.Parse(networkConfig.ExternalIp) : IPAddress.Loopback; return new Enode(nodeKey.PublicKey, ipAddress, networkConfig.P2PPort); }) - .AddAdvance(cfg => - { - cfg.As(); - cfg.WithParameter(TypedParameter.From(nodeKey)); - }) - .AddKeyedSingleton(NodeKey, nodeKey) .AddSingleton((ctx) => @@ -69,16 +64,23 @@ protected override void Load(ContainerBuilder builder) // It just need to override this. HasSynced = true }; - }); + }) - builder - .RegisterBuildCallback((cfg) => + .AddDecorator((_, syncConfig) => { - ISyncConfig syncConfig = cfg.Resolve(); syncConfig.GCOnFeedFinished = false; syncConfig.MultiSyncModeSelectorLoopTimerMs = 1; syncConfig.SyncDispatcherEmptyRequestDelayMs = 1; syncConfig.SyncDispatcherAllocateTimeoutMs = 1; + return syncConfig; + }) + .AddDecorator((_, networkConfig) => + { + networkConfig.DiscoveryDns = null; + networkConfig.LocalIp ??= "127.0.0.1"; + networkConfig.ExternalIp ??= "127.0.0.1"; + networkConfig.RlpxHostShutdownCloseTimeoutMs = 1; + return networkConfig; }); } } diff --git a/src/Nethermind/Nethermind.Crypto/IProtectedPrivateKey.cs b/src/Nethermind/Nethermind.Crypto/IProtectedPrivateKey.cs new file mode 100644 index 00000000000..f5352513a3c --- /dev/null +++ b/src/Nethermind/Nethermind.Crypto/IProtectedPrivateKey.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Crypto; + +namespace Nethermind.Crypto; + +public interface IProtectedPrivateKey +{ + public const string NodeKey = "NodeKey"; + + PublicKey PublicKey { get; } + CompressedPublicKey CompressedPublicKey { get; } + PrivateKey Unprotect(); +} diff --git a/src/Nethermind/Nethermind.Crypto/ProtectedPrivateKey.cs b/src/Nethermind/Nethermind.Crypto/ProtectedPrivateKey.cs index f8e90897c98..1801c5b6a0d 100644 --- a/src/Nethermind/Nethermind.Crypto/ProtectedPrivateKey.cs +++ b/src/Nethermind/Nethermind.Crypto/ProtectedPrivateKey.cs @@ -6,7 +6,7 @@ namespace Nethermind.Crypto { - public class ProtectedPrivateKey : ProtectedData + public class ProtectedPrivateKey : ProtectedData, IProtectedPrivateKey { public ProtectedPrivateKey(PrivateKey privateKey, string keyStoreDir, ICryptoRandom? random = null, ITimestamper? timestamper = null) diff --git a/src/Nethermind/Nethermind.ExternalSigner.Plugin/ClefSigner.cs b/src/Nethermind/Nethermind.ExternalSigner.Plugin/ClefSigner.cs index 5a6c7641365..671e539f158 100644 --- a/src/Nethermind/Nethermind.ExternalSigner.Plugin/ClefSigner.cs +++ b/src/Nethermind/Nethermind.ExternalSigner.Plugin/ClefSigner.cs @@ -106,7 +106,7 @@ private static async Task
GetSignerAddress(IJsonRpcClient rpcClient, Ad public void SetSigner(PrivateKey key) => ThrowInvalidOperationSetSigner(); - public void SetSigner(ProtectedPrivateKey key) => ThrowInvalidOperationSetSigner(); + public void SetSigner(IProtectedPrivateKey key) => ThrowInvalidOperationSetSigner(); [DoesNotReturn] [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/Nethermind/Nethermind.Init/Steps/InitializeNetwork.cs b/src/Nethermind/Nethermind.Init/Steps/InitializeNetwork.cs index 1c6c2ad2d18..e04ca016a41 100644 --- a/src/Nethermind/Nethermind.Init/Steps/InitializeNetwork.cs +++ b/src/Nethermind/Nethermind.Init/Steps/InitializeNetwork.cs @@ -257,8 +257,6 @@ private void InitDiscovery() _api.LogManager, _api.Timestamper, _api.CryptoRandom, _api.NodeStatsManager, _api.IpResolver ); - - _api.DiscoveryApp.Initialize(_api.NodeKey.PublicKey); } private Task StartSync() @@ -335,7 +333,7 @@ private async Task InitPeer() _api.SessionMonitor = new SessionMonitor(_networkConfig, _api.LogManager); _api.RlpxPeer = new RlpxHost( _api.MessageSerializationService, - _api.Enode!, + _api.NodeKey!, encryptionHandshakeServiceA, _api.SessionMonitor, _api.DisconnectsAnalyzer, diff --git a/src/Nethermind/Nethermind.Init/Steps/SetupKeyStore.cs b/src/Nethermind/Nethermind.Init/Steps/SetupKeyStore.cs index 1d5a14b2b56..9db80d00fa3 100644 --- a/src/Nethermind/Nethermind.Init/Steps/SetupKeyStore.cs +++ b/src/Nethermind/Nethermind.Init/Steps/SetupKeyStore.cs @@ -59,7 +59,7 @@ await Task.Run(() => .OrReadFromConsole($"Provide password for validator account {keyStoreConfig.BlockAuthorAccount}"); INodeKeyManager nodeKeyManager = new NodeKeyManager(get.CryptoRandom, get.KeyStore, keyStoreConfig, get.LogManager, passwordProvider, get.FileSystem); - ProtectedPrivateKey? nodeKey = set.NodeKey = nodeKeyManager.LoadNodeKey(); + IProtectedPrivateKey? nodeKey = set.NodeKey = nodeKeyManager.LoadNodeKey(); IMiningConfig miningConfig = get.Config(); //Don't load the local key if an external signer is configured diff --git a/src/Nethermind/Nethermind.Network.Contract/Config/INetworkConfig.cs b/src/Nethermind/Nethermind.Network.Contract/Config/INetworkConfig.cs index 83bfdf6924b..978faa8c78e 100644 --- a/src/Nethermind/Nethermind.Network.Contract/Config/INetworkConfig.cs +++ b/src/Nethermind/Nethermind.Network.Contract/Config/INetworkConfig.cs @@ -100,4 +100,7 @@ public interface INetworkConfig : IConfig [ConfigItem(DefaultValue = "false", HiddenFromDocs = true, Description = "[TECHNICAL] Disable feeding ENR DNS records to discv4 table")] bool DisableDiscV4DnsFeeder { get; set; } + + [ConfigItem(DefaultValue = "false", HiddenFromDocs = true, Description = "[TECHNICAL] Shutdown timeout when closing TCP port.")] + long RlpxHostShutdownCloseTimeoutMs { get; set; } } diff --git a/src/Nethermind/Nethermind.Network.Discovery.Test/E2EDiscoveryTests.cs b/src/Nethermind/Nethermind.Network.Discovery.Test/E2EDiscoveryTests.cs new file mode 100644 index 00000000000..a346573f033 --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Discovery.Test/E2EDiscoveryTests.cs @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Autofac; +using Nethermind.Config; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Test.Builders; +using Nethermind.Core.Test.Modules; +using Nethermind.Crypto; +using Nethermind.Logging; +using Nethermind.Network.Config; +using Nethermind.Serialization.Json; +using Nethermind.Specs.ChainSpecStyle; +using NUnit.Framework; + +namespace Nethermind.Network.Discovery.Test; + +[TestFixture(DiscoveryVersion.V4)] +[TestFixture(DiscoveryVersion.V5)] +public class E2EDiscoveryTests(DiscoveryVersion discoveryVersion) +{ + private static TimeSpan TestTimeout = TimeSpan.FromSeconds(5); + + /// + /// Common code for all node + /// + private IContainer CreateNode(PrivateKey nodeKey, IEnode? bootEnode = null) + { + IConfigProvider configProvider = new ConfigProvider(); + ChainSpec spec = new ChainSpecLoader(new EthereumJsonSerializer()).LoadEmbeddedOrFromFile("chainspec/foundation.json", default); + spec.Bootnodes = []; + if (bootEnode is not null) + { + spec.Bootnodes = [new(bootEnode.PublicKey, bootEnode.HostIp.ToString(), bootEnode.Port)]; + } + + INetworkConfig networkConfig = configProvider.GetConfig(); + int port = AssignDiscoveryPort(); + networkConfig.DiscoveryPort = port; + networkConfig.P2PPort = port; + + IDiscoveryConfig discoveryConfig = configProvider.GetConfig(); + discoveryConfig.DiscoveryVersion = discoveryVersion; + + return new ContainerBuilder() + .AddModule(new PseudoNethermindModule(spec, configProvider, new TestLogManager())) + .AddModule(new TestEnvironmentModule(nodeKey, $"{nameof(E2EDiscoveryTests)}-{discoveryVersion}")) + .Build(); + } + + int _discoveryPort = 0; + private int AssignDiscoveryPort() + { + return Interlocked.Increment(ref _discoveryPort); + } + + [Test] + public async Task TestDiscovery() + { + if (discoveryVersion == DiscoveryVersion.V5) Assert.Ignore("DiscV5 does not seems to work."); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource().ThatCancelAfter(TestTimeout); + + await using IContainer boot = CreateNode(TestItem.PrivateKeys[0]); + IEnode bootEnode = boot.Resolve(); + await using IContainer node1 = CreateNode(TestItem.PrivateKeys[1], bootEnode); + await using IContainer node2 = CreateNode(TestItem.PrivateKeys[2], bootEnode); + await using IContainer node3 = CreateNode(TestItem.PrivateKeys[3], bootEnode); + await using IContainer node4 = CreateNode(TestItem.PrivateKeys[4], bootEnode); + + IContainer[] nodes = [boot, node1, node2, node3, node4]; + + HashSet nodeKeys = nodes.Select(ctx => ctx.Resolve().PublicKey).ToHashSet(); + + foreach (IContainer node in nodes) + { + await node.Resolve().StartDiscovery(cancellationTokenSource.Token); + } + + foreach (IContainer node in nodes) + { + IPeerPool pool = node.Resolve(); + HashSet expectedKeys = new HashSet(nodeKeys); + expectedKeys.Remove(node.Resolve().PublicKey); + + Assert.That(() => pool.Peers.Values.Select((p) => p.Node.Id).ToHashSet(), Is.EquivalentTo(expectedKeys).After(1000, 100)); + } + } +} diff --git a/src/Nethermind/Nethermind.Network.Discovery/CompositeDiscoveryApp.cs b/src/Nethermind/Nethermind.Network.Discovery/CompositeDiscoveryApp.cs index 3bfbe910efb..bfcbe008699 100644 --- a/src/Nethermind/Nethermind.Network.Discovery/CompositeDiscoveryApp.cs +++ b/src/Nethermind/Nethermind.Network.Discovery/CompositeDiscoveryApp.cs @@ -3,6 +3,7 @@ using System.Net.Sockets; using System.Runtime.InteropServices; +using Autofac.Features.AttributeFilters; using DotNetty.Transport.Bootstrapping; using DotNetty.Transport.Channels; using DotNetty.Transport.Channels.Sockets; @@ -31,7 +32,7 @@ public class CompositeDiscoveryApp : IDiscoveryApp { private const string DiscoveryNodesDbPath = "discoveryNodes"; - private readonly ProtectedPrivateKey _nodeKey; + private readonly IProtectedPrivateKey _nodeKey; private readonly INetworkConfig _networkConfig; private readonly IDiscoveryConfig _discoveryConfig; private readonly IInitConfig _initConfig; @@ -43,16 +44,19 @@ public class CompositeDiscoveryApp : IDiscoveryApp private readonly INodeStatsManager _nodeStatsManager; private readonly IIPResolver _ipResolver; private readonly IConnectionsPool _connections; + private readonly IChannelFactory? _channelFactory; private IDiscoveryApp? _v4; private IDiscoveryApp? _v5; private INodeSource _compositeNodeSource = null!; - public CompositeDiscoveryApp(ProtectedPrivateKey? nodeKey, + public CompositeDiscoveryApp( + [KeyFilter(IProtectedPrivateKey.NodeKey)] + IProtectedPrivateKey? nodeKey, INetworkConfig networkConfig, IDiscoveryConfig discoveryConfig, IInitConfig initConfig, IEthereumEcdsa? ethereumEcdsa, IMessageSerializationService? serializationService, ILogManager? logManager, ITimestamper? timestamper, ICryptoRandom? cryptoRandom, - INodeStatsManager? nodeStatsManager, IIPResolver? ipResolver + INodeStatsManager? nodeStatsManager, IIPResolver? ipResolver, IChannelFactory? channelFactory = null ) { _nodeKey = nodeKey ?? throw new ArgumentNullException(nameof(nodeKey)); @@ -67,6 +71,9 @@ public CompositeDiscoveryApp(ProtectedPrivateKey? nodeKey, _nodeStatsManager = nodeStatsManager ?? throw new ArgumentNullException(nameof(nodeStatsManager)); _ipResolver = ipResolver ?? throw new ArgumentNullException(nameof(ipResolver)); _connections = new DiscoveryConnectionsPool(logManager.GetClassLogger(), _networkConfig, _discoveryConfig); + _channelFactory = channelFactory; + + Initialize(nodeKey.PublicKey); } public void Initialize(PublicKey masterPublicKey) @@ -102,7 +109,9 @@ public async Task StartAsync() Bootstrap bootstrap = new(); bootstrap.Group(new MultithreadEventLoopGroup(1)); - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + if (_channelFactory is not null) + bootstrap.ChannelFactory(() => _channelFactory!.CreateDatagramChannel()); + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) bootstrap.ChannelFactory(static () => new SocketDatagramChannel(AddressFamily.InterNetwork)); else bootstrap.Channel(); @@ -118,10 +127,10 @@ await Task.WhenAll( } public Task StopAsync() => Task.WhenAll( - _connections.StopAsync(), - _v4?.StopAsync() ?? Task.CompletedTask, - _v5?.StopAsync() ?? Task.CompletedTask - ); + _connections.StopAsync(), + _v4?.StopAsync() ?? Task.CompletedTask, + _v5?.StopAsync() ?? Task.CompletedTask + ); public void AddNodeToDiscovery(Node node) { diff --git a/src/Nethermind/Nethermind.Network.Dns/EnrDiscovery.cs b/src/Nethermind/Nethermind.Network.Dns/EnrDiscovery.cs index 3ef840b657f..ba635a4c60e 100644 --- a/src/Nethermind/Nethermind.Network.Dns/EnrDiscovery.cs +++ b/src/Nethermind/Nethermind.Network.Dns/EnrDiscovery.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Diagnostics.CodeAnalysis; using System.Net; using System.Runtime.CompilerServices; using DnsClient; @@ -30,6 +31,8 @@ public EnrDiscovery(IEnrRecordParser parser, INetworkConfig networkConfig, ILogM public async IAsyncEnumerable DiscoverNodes([EnumeratorCancellation] CancellationToken cancellationToken) { + if (string.IsNullOrWhiteSpace(_domain)) yield break; + IByteBuffer buffer = PooledByteBufferAllocator.Default.Buffer(); await using ConfiguredCancelableAsyncEnumerable.Enumerator enumerator = _crawler.SearchTree(_domain) .WithCancellation(cancellationToken) diff --git a/src/Nethermind/Nethermind.Network.Test/Rlpx/RlpxPeerTests.cs b/src/Nethermind/Nethermind.Network.Test/Rlpx/RlpxPeerTests.cs index 259684132da..3167063db3a 100644 --- a/src/Nethermind/Nethermind.Network.Test/Rlpx/RlpxPeerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/Rlpx/RlpxPeerTests.cs @@ -6,7 +6,9 @@ using System.Net.Sockets; using System.Threading.Tasks; using Nethermind.Core.Test.Builders; +using Nethermind.Core.Test.Modules; using Nethermind.Logging; +using Nethermind.Network.Config; using Nethermind.Network.P2P.Analyzers; using Nethermind.Network.Rlpx; using Nethermind.Network.Rlpx.Handshake; @@ -24,16 +26,19 @@ public async Task Start_stop() { RlpxHost host = new( Substitute.For(), - TestItem.PublicKeyA, - 1, - GegAvailableLocalPort(), - null, - 2000, + new InsecureProtectedPrivateKey(TestItem.PrivateKeyA), Substitute.For(), Substitute.For(), NullDisconnectsAnalyzer.Instance, - LimboLogs.Instance, - TimeSpan.Zero); + new NetworkConfig() + { + ProcessingThreadCount = 1, + P2PPort = GegAvailableLocalPort(), + LocalIp = null, + ConnectTimeoutMs = 200, + SimulateSendLatencyMs = 0, + }, + LimboLogs.Instance); await host.Init(); await host.Shutdown(); } diff --git a/src/Nethermind/Nethermind.Network/Config/NetworkConfig.cs b/src/Nethermind/Nethermind.Network/Config/NetworkConfig.cs index 82d36422a1a..db79eeb89f5 100644 --- a/src/Nethermind/Nethermind.Network/Config/NetworkConfig.cs +++ b/src/Nethermind/Nethermind.Network/Config/NetworkConfig.cs @@ -40,5 +40,6 @@ public class NetworkConfig : INetworkConfig public int ProcessingThreadCount { get; set; } = 1; public string? ClientIdMatcher { get; set; } = null; public bool DisableDiscV4DnsFeeder { get; set; } = false; + public long RlpxHostShutdownCloseTimeoutMs { get; set; } = 1000; } } diff --git a/src/Nethermind/Nethermind.Network/IChannelFactory.cs b/src/Nethermind/Nethermind.Network/IChannelFactory.cs index 8977c9fe5ad..bf99027ee58 100644 --- a/src/Nethermind/Nethermind.Network/IChannelFactory.cs +++ b/src/Nethermind/Nethermind.Network/IChannelFactory.cs @@ -7,7 +7,9 @@ namespace Nethermind.Network; public interface IChannelFactory { - public IServerChannel CreateServer(); + IServerChannel CreateServer(); - public IChannel CreateClient(); + IChannel CreateClient(); + + IChannel CreateDatagramChannel(); } diff --git a/src/Nethermind/Nethermind.Network/NodesLoader.cs b/src/Nethermind/Nethermind.Network/NodesLoader.cs index 8bbcabb6d79..c0a3a10e2a5 100644 --- a/src/Nethermind/Nethermind.Network/NodesLoader.cs +++ b/src/Nethermind/Nethermind.Network/NodesLoader.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; +using Autofac.Features.AttributeFilters; using Nethermind.Config; using Nethermind.Logging; using Nethermind.Network.Config; @@ -28,7 +29,7 @@ public class NodesLoader : INodeSource public NodesLoader( INetworkConfig networkConfig, INodeStatsManager stats, - INetworkStorage peerStorage, + [KeyFilter(INetworkStorage.PeerDb)] INetworkStorage peerStorage, IRlpxHost rlpxHost, ILogManager logManager) { diff --git a/src/Nethermind/Nethermind.Network/PeerPool.cs b/src/Nethermind/Nethermind.Network/PeerPool.cs index d7a3045f41b..bfb0177d48d 100644 --- a/src/Nethermind/Nethermind.Network/PeerPool.cs +++ b/src/Nethermind/Nethermind.Network/PeerPool.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Autofac.Features.AttributeFilters; using Nethermind.Config; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; @@ -48,7 +49,7 @@ public class PeerPool : IPeerPool public PeerPool( INodeSource nodeSource, INodeStatsManager nodeStatsManager, - INetworkStorage peerStorage, + [KeyFilter(INetworkStorage.PeerDb)] INetworkStorage peerStorage, INetworkConfig networkConfig, ILogManager logManager) { diff --git a/src/Nethermind/Nethermind.Network/Rlpx/Handshake/HandshakeService.cs b/src/Nethermind/Nethermind.Network/Rlpx/Handshake/HandshakeService.cs index 94d84b3ec1a..9263f049e6c 100644 --- a/src/Nethermind/Nethermind.Network/Rlpx/Handshake/HandshakeService.cs +++ b/src/Nethermind/Nethermind.Network/Rlpx/Handshake/HandshakeService.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using Autofac.Features.AttributeFilters; using DotNetty.Buffers; using DotNetty.Common.Utilities; using Nethermind.Core.Crypto; @@ -29,6 +30,16 @@ public class HandshakeService : IHandshakeService private readonly ILogger _logger; private readonly IEcdsa _ecdsa; + public HandshakeService( + IMessageSerializationService messageSerializationService, + IEciesCipher eciesCipher, + ICryptoRandom cryptoRandom, + IEcdsa ecdsa, + [KeyFilter(IProtectedPrivateKey.NodeKey)] IProtectedPrivateKey nodeKey, + ILogManager logManager) + : this(messageSerializationService, eciesCipher, cryptoRandom, ecdsa, nodeKey.Unprotect(), logManager) + { } + public HandshakeService( IMessageSerializationService messageSerializationService, IEciesCipher eciesCipher, diff --git a/src/Nethermind/Nethermind.Network/Rlpx/RlpxHost.cs b/src/Nethermind/Nethermind.Network/Rlpx/RlpxHost.cs index 5bf9f75cf0d..9cabc1cb2a8 100644 --- a/src/Nethermind/Nethermind.Network/Rlpx/RlpxHost.cs +++ b/src/Nethermind/Nethermind.Network/Rlpx/RlpxHost.cs @@ -5,14 +5,15 @@ using System.Net; using System.Threading; using System.Threading.Tasks; +using Autofac.Features.AttributeFilters; using DotNetty.Common.Concurrency; using DotNetty.Handlers.Logging; using DotNetty.Transport.Bootstrapping; using DotNetty.Transport.Channels; using DotNetty.Transport.Channels.Sockets; -using Nethermind.Config; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; +using Nethermind.Crypto; using Nethermind.Logging; using Nethermind.Network.Config; using Nethermind.Network.P2P; @@ -33,11 +34,11 @@ public class RlpxHost : IRlpxHost private bool _isInitialized; public PublicKey LocalNodeId { get; } public int LocalPort { get; } - public string? LocalIp { get; set; } + private string? LocalIp { get; } private readonly IHandshakeService _handshakeService; private readonly IMessageSerializationService _serializationService; private readonly ILogManager _logManager; - private readonly Logging.ILogger _logger; + private readonly ILogger _logger; private readonly ISessionMonitor _sessionMonitor; private readonly IDisconnectsAnalyzer _disconnectsAnalyzer; private readonly IEventExecutorGroup _group; @@ -45,45 +46,18 @@ public class RlpxHost : IRlpxHost private readonly TimeSpan _connectTimeout; private readonly IChannelFactory? _channelFactory; - public RlpxHost( - IMessageSerializationService serializationService, - IEnode localEnode, - IHandshakeService handshakeService, - ISessionMonitor sessionMonitor, - IDisconnectsAnalyzer disconnectsAnalyzer, - INetworkConfig networkConfig, - ILogManager logManager, - IChannelFactory? channelFactory = null - ) : this( - serializationService, - localEnode.PublicKey, - networkConfig.ProcessingThreadCount, - networkConfig.P2PPort, - networkConfig.LocalIp, - networkConfig.ConnectTimeoutMs, - handshakeService, - sessionMonitor, - disconnectsAnalyzer, - logManager, - TimeSpan.FromMilliseconds(networkConfig.SimulateSendLatencyMs), - channelFactory - ) - { } + private readonly TimeSpan _shutdownQuietPeriod; + private readonly TimeSpan _shutdownCloseTimeout; public RlpxHost( IMessageSerializationService serializationService, - PublicKey localNodeId, - int networkProcessingThread, - int localPort, - string? localIp, - int connectTimeoutMs, + [KeyFilter(IProtectedPrivateKey.NodeKey)] IProtectedPrivateKey nodeKey, IHandshakeService handshakeService, ISessionMonitor sessionMonitor, IDisconnectsAnalyzer disconnectsAnalyzer, + INetworkConfig networkConfig, ILogManager logManager, - TimeSpan sendLatency, - IChannelFactory? channelFactory = null - ) + IChannelFactory? channelFactory = null) { // .NET Core definitely got the easy logging setup right :D // ResourceLeakDetector.Level = ResourceLeakDetector.DetectionLevel.Paranoid; @@ -100,6 +74,7 @@ public RlpxHost( // new LoggerFilterOptions { MinLevel = Microsoft.Extensions.Logging.LogLevel.Warning }); // InternalLoggerFactory.DefaultFactory = loggerFactory; + int networkProcessingThread = networkConfig.ProcessingThreadCount; if (networkProcessingThread <= 1) { _group = new SingleThreadEventLoop(); @@ -114,12 +89,14 @@ public RlpxHost( _sessionMonitor = sessionMonitor ?? throw new ArgumentNullException(nameof(sessionMonitor)); _disconnectsAnalyzer = disconnectsAnalyzer ?? throw new ArgumentNullException(nameof(disconnectsAnalyzer)); _handshakeService = handshakeService ?? throw new ArgumentNullException(nameof(handshakeService)); - LocalNodeId = localNodeId ?? throw new ArgumentNullException(nameof(localNodeId)); - LocalPort = localPort; - LocalIp = localIp; - _sendLatency = sendLatency; - _connectTimeout = TimeSpan.FromMilliseconds(connectTimeoutMs); + LocalNodeId = nodeKey.PublicKey; + LocalPort = networkConfig.P2PPort; + LocalIp = networkConfig.LocalIp; + _sendLatency = TimeSpan.FromMilliseconds(networkConfig.SimulateSendLatencyMs); + _connectTimeout = TimeSpan.FromMilliseconds(networkConfig.ConnectTimeoutMs); _channelFactory = channelFactory; + _shutdownQuietPeriod = TimeSpan.FromMilliseconds(Math.Min(networkConfig.RlpxHostShutdownCloseTimeoutMs, 100)); + _shutdownCloseTimeout = TimeSpan.FromMilliseconds(networkConfig.RlpxHostShutdownCloseTimeoutMs); } public async Task Init() @@ -294,7 +271,6 @@ private void SessionOnPeerDisconnected(object sender, DisconnectEventArgs e) public async Task Shutdown() { - // InternalLoggerFactory.DefaultFactory.AddProvider(new ConsoleLoggerProvider((s, level) => true, false)); await (_bootstrapChannel?.CloseAsync().ContinueWith(t => { if (t.IsFaulted) @@ -305,12 +281,9 @@ public async Task Shutdown() if (_logger.IsDebug) _logger.Debug("Closed _bootstrapChannel"); - // every [quietPeriod] we check if there were any event in the loop - if none then we can shutdown - TimeSpan quietPeriod = TimeSpan.FromMilliseconds(100); - TimeSpan nettyCloseTimeout = TimeSpan.FromMilliseconds(1000); Task closingTask = Task.WhenAll( - _bossGroup is not null ? _bossGroup.ShutdownGracefullyAsync(quietPeriod, nettyCloseTimeout) : Task.CompletedTask, - _workerGroup is not null ? _workerGroup.ShutdownGracefullyAsync(nettyCloseTimeout, nettyCloseTimeout) : Task.CompletedTask); + _bossGroup is not null ? _bossGroup.ShutdownGracefullyAsync(_shutdownQuietPeriod, _shutdownCloseTimeout) : Task.CompletedTask, + _workerGroup is not null ? _workerGroup.ShutdownGracefullyAsync(_shutdownCloseTimeout, _shutdownCloseTimeout) : Task.CompletedTask); // below comment may arise from not understanding the quiet period but the resolution is correct // we need to add additional timeout on our side as netty is not executing internal timeout properly, often it just hangs forever on closing