From 5d206a330d81891f36f2be314d0cbef6a652e548 Mon Sep 17 00:00:00 2001 From: Richard Gregory Date: Wed, 11 Dec 2024 08:30:58 +0100 Subject: [PATCH 01/15] Implement admin_addTrustedPeer rpc endpoint --- .../Nethermind.Api/IApiWithNetwork.cs | 1 + src/Nethermind/Nethermind.Api/IInitConfig.cs | 3 + src/Nethermind/Nethermind.Api/InitConfig.cs | 1 + .../Nethermind.Api/NethermindApi.cs | 1 + .../Nethermind.Cli/Modules/AdminCliModule.cs | 4 + .../Steps/InitializeNetwork.cs | 9 +- .../Steps/RegisterRpcModules.cs | 5 + .../Modules/AdminModuleTests.cs | 2 + .../Modules/Admin/AdminRpcModule.cs | 21 ++- .../Modules/Admin/IAdminRpcModule.cs | 10 + .../Nethermind.Network.Stats/Model/Node.cs | 3 + .../PeerManagerTests.cs | 3 +- .../Nethermind.Network.Test/PeerPoolTests.cs | 5 +- src/Nethermind/Nethermind.Network/PeerPool.cs | 22 ++- .../TrustedNodes/TrustedNodesManager.cs | 175 ++++++++++++++++++ .../iTrustedNodesManager.cs | 18 ++ 16 files changed, 275 insertions(+), 8 deletions(-) create mode 100644 src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs create mode 100644 src/Nethermind/Nethermind.Network/iTrustedNodesManager.cs diff --git a/src/Nethermind/Nethermind.Api/IApiWithNetwork.cs b/src/Nethermind/Nethermind.Api/IApiWithNetwork.cs index 5abb6d545e6..71bb2779235 100644 --- a/src/Nethermind/Nethermind.Api/IApiWithNetwork.cs +++ b/src/Nethermind/Nethermind.Api/IApiWithNetwork.cs @@ -44,6 +44,7 @@ public interface IApiWithNetwork : IApiWithBlockchain IJsonRpcLocalStats? JsonRpcLocalStats { get; set; } ISessionMonitor? SessionMonitor { get; set; } IStaticNodesManager? StaticNodesManager { get; set; } + ITrustedNodesManager? TrustedNodesManager { get; set; } ISynchronizer? Synchronizer { get; } ISyncModeSelector SyncModeSelector { get; } ISyncProgressResolver? SyncProgressResolver { get; } diff --git a/src/Nethermind/Nethermind.Api/IInitConfig.cs b/src/Nethermind/Nethermind.Api/IInitConfig.cs index ff826142e9c..980a1f74aa5 100644 --- a/src/Nethermind/Nethermind.Api/IInitConfig.cs +++ b/src/Nethermind/Nethermind.Api/IInitConfig.cs @@ -45,6 +45,9 @@ public interface IInitConfig : IConfig [ConfigItem(Description = "The path to the static nodes file.", DefaultValue = "Data/static-nodes.json")] string StaticNodesPath { get; set; } + [ConfigItem(Description = "The path to the trusted nodes file.", DefaultValue = "Data/trusted-nodes.json")] + string TrustedNodesPath { get; set; } + [ConfigItem(Description = "The name of the log file.", DefaultValue = "log.txt")] string LogFileName { get; set; } diff --git a/src/Nethermind/Nethermind.Api/InitConfig.cs b/src/Nethermind/Nethermind.Api/InitConfig.cs index ed772d22b4e..f7e54eece07 100644 --- a/src/Nethermind/Nethermind.Api/InitConfig.cs +++ b/src/Nethermind/Nethermind.Api/InitConfig.cs @@ -20,6 +20,7 @@ public class InitConfig : IInitConfig public string LogFileName { get; set; } = "log.txt"; public string? GenesisHash { get; set; } public string StaticNodesPath { get; set; } = "Data/static-nodes.json"; + public string TrustedNodesPath { get; set; } = "Data/trusted-nodes.json"; public string? KzgSetupPath { get; set; } = null; public string LogDirectory { get; set; } = "logs"; public string? LogRules { get; set; } = null; diff --git a/src/Nethermind/Nethermind.Api/NethermindApi.cs b/src/Nethermind/Nethermind.Api/NethermindApi.cs index 76b15df071f..f793c5e1a80 100644 --- a/src/Nethermind/Nethermind.Api/NethermindApi.cs +++ b/src/Nethermind/Nethermind.Api/NethermindApi.cs @@ -204,6 +204,7 @@ public ISealEngine SealEngine public IVerifyTrieStarter? VerifyTrieStarter { get; set; } public IStateReader? StateReader { get; set; } public IStaticNodesManager? StaticNodesManager { get; set; } + public ITrustedNodesManager? TrustedNodesManager { get; set; } public ITimestamper Timestamper { get; } = Core.Timestamper.Default; public ITimerFactory TimerFactory { get; } = Core.Timers.TimerFactory.Default; public ITransactionProcessor? TransactionProcessor { get; set; } diff --git a/src/Nethermind/Nethermind.Cli/Modules/AdminCliModule.cs b/src/Nethermind/Nethermind.Cli/Modules/AdminCliModule.cs index 9193eed4593..4045a41c6be 100644 --- a/src/Nethermind/Nethermind.Cli/Modules/AdminCliModule.cs +++ b/src/Nethermind/Nethermind.Cli/Modules/AdminCliModule.cs @@ -21,5 +21,9 @@ public AdminCliModule(ICliEngine engine, INodeManager nodeManager) : base(engine [CliFunction("admin", "removePeer", Description = "Removes given node from the static nodes")] public string? RemovePeer(string enode, bool removeFromStaticNodes = false) => NodeManager.Post("admin_removePeer", enode, removeFromStaticNodes).Result; + + [CliFunction("admin", "addTrustedPeer", Description = "Adds given node to the trusted nodes")] + public bool AddTrustedPeer(string enode) => NodeManager.Post("admin_addTrustedPeer", enode).Result; + } } diff --git a/src/Nethermind/Nethermind.Init/Steps/InitializeNetwork.cs b/src/Nethermind/Nethermind.Init/Steps/InitializeNetwork.cs index 83b50601abe..41fb4135c49 100644 --- a/src/Nethermind/Nethermind.Init/Steps/InitializeNetwork.cs +++ b/src/Nethermind/Nethermind.Init/Steps/InitializeNetwork.cs @@ -352,6 +352,9 @@ private async Task InitPeer() _api.StaticNodesManager = new StaticNodesManager(initConfig.StaticNodesPath, _api.LogManager); await _api.StaticNodesManager.InitAsync(); + _api.TrustedNodesManager = new TrustedNodesManager(initConfig.TrustedNodesPath, _api.LogManager); + await _api.TrustedNodesManager.InitAsync(); + // ToDo: PeersDB is registered outside dbProvider string dbName = INetworkStorage.PeerDb; IFullDb peersDb = initConfig.DiagnosticMode == DiagnosticMode.MemDb @@ -417,9 +420,9 @@ private async Task InitPeer() } CompositeNodeSource nodeSources = _networkConfig.OnlyStaticPeers - ? new(_api.StaticNodesManager, nodesLoader) - : new(_api.StaticNodesManager, nodesLoader, enrDiscovery, _api.DiscoveryApp); - _api.PeerPool = new PeerPool(nodeSources, _api.NodeStatsManager, peerStorage, _networkConfig, _api.LogManager); + ? new(_api.StaticNodesManager, _api.TrustedNodesManager, nodesLoader) + : new(_api.StaticNodesManager, _api.TrustedNodesManager, nodesLoader, enrDiscovery, _api.DiscoveryApp); + _api.PeerPool = new PeerPool(nodeSources, _api.NodeStatsManager, peerStorage, _networkConfig, _api.LogManager, _api.TrustedNodesManager); _api.PeerManager = new PeerManager( _api.RlpxPeer, _api.PeerPool, diff --git a/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs b/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs index e418ee52e78..e646e5a9c94 100644 --- a/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs +++ b/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs @@ -120,6 +120,10 @@ public virtual async Task Execute(CancellationToken cancellationToken) _api.KeyStore); rpcModuleProvider.RegisterSingle(personalRpcModule); + StepDependencyException.ThrowIfNull(_api.PeerManager); + StepDependencyException.ThrowIfNull(_api.StaticNodesManager); + StepDependencyException.ThrowIfNull(_api.Enode); + ManualPruningTrigger pruningTrigger = new(); _api.PruningTrigger?.Add(pruningTrigger); (IApiWithStores getFromApi, IApiWithBlockchain setInApi) = _api.ForInit; @@ -141,6 +145,7 @@ public virtual async Task Execute(CancellationToken cancellationToken) initConfig.BaseDbPath, pruningTrigger, getFromApi.ChainSpec.Parameters, + _api.TrustedNodesManager, subscriptionManager); rpcModuleProvider.RegisterSingle(adminRpcModule); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs index 87308907027..5d3fba80924 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs @@ -90,6 +90,7 @@ public void Setup() IJsonSerializer jsonSerializer = new EthereumJsonSerializer(); IStaticNodesManager staticNodesManager = Substitute.For(); + ITrustedNodesManager trustedNodesManager = Substitute.For(); Enode enode = new(_enodeString); ChainSpec chainSpec = new() { @@ -116,6 +117,7 @@ public void Setup() _exampleDataDir, new ManualPruningTrigger(), chainSpec.Parameters, + trustedNodesManager, _subscriptionManager); _adminRpcModule.Context = new JsonRpcContext(RpcEndpoint.Ws, _jsonRpcDuplexClient); diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs index 785d3baa339..3b41de5e6f0 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs @@ -37,6 +37,7 @@ public class AdminRpcModule : IAdminRpcModule private readonly IStateReader _stateReader; private NodeInfo _nodeInfo = null!; private readonly IAdminEraService _eraService; + private readonly ITrustedNodesManager _trustedNodesManager; private readonly ISubscriptionManager _subscriptionManager; public AdminRpcModule( @@ -50,8 +51,7 @@ public AdminRpcModule( IAdminEraService eraService, string dataDir, ManualPruningTrigger pruningTrigger, - ChainParameters parameters, - ISubscriptionManager subscriptionManager) + ChainParameters parameters) { _enode = enode ?? throw new ArgumentNullException(nameof(enode)); _dataDir = dataDir ?? throw new ArgumentNullException(nameof(dataDir)); @@ -64,6 +64,7 @@ public AdminRpcModule( _pruningTrigger = pruningTrigger; _eraService = eraService; _parameters = parameters ?? throw new ArgumentNullException(nameof(parameters)); + _trustedNodesManager = trustedNodesManager ?? throw new ArgumentNullException(nameof(trustedNodesManager)); BuildNodeInfo(); _subscriptionManager = subscriptionManager; @@ -134,6 +135,22 @@ public async Task> admin_removePeer(string enode, bool rem : ResultWrapper.Fail("Failed to remove peer."); } + public async Task> admin_addTrustedPeer(string enode) + { + bool added = await _trustedNodesManager.AddAsync(enode); + if (added) + { + _peerPool.GetOrAdd(new NetworkNode(enode)); + + return ResultWrapper.Success(true); + } + else + { + return ResultWrapper.Fail("Failed to add trusted peer."); + } + } + + public ResultWrapper admin_peers(bool includeDetails = false) => ResultWrapper.Success( _peerPool.ActivePeers.Select(p => new PeerInfo(p.Value, includeDetails)).ToArray()); diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/IAdminRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/IAdminRpcModule.cs index b7b7aa9f5b3..6defc9fc204 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/IAdminRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/IAdminRpcModule.cs @@ -108,6 +108,16 @@ Task> admin_importHistory( IsImplemented = true)] ResultWrapper admin_verifyTrie(BlockParameter block); + [JsonRpcMethod(Description = "Adds given node as a trusted peer, allowing the node to always connect even if slots are full.", + EdgeCaseHint = "", + ResponseDescription = "Boolean indicating success", + ExampleResponse = "true", + IsImplemented = true)] + Task> admin_addTrustedPeer( + [JsonRpcParameter(Description = "Given node", ExampleValue = "\"enode://...\"")] + string enode +); + [JsonRpcMethod(Description = "Subscribes to a particular event over WebSocket. For every event that matches the subscription, a notification with event details and subscription id is sent to a client.", IsImplemented = true, IsSharable = false, Availability = RpcEndpoint.All & ~RpcEndpoint.Http)] ResultWrapper admin_subscribe(string subscriptionName, string? args = null); [JsonRpcMethod(Description = "Unsubscribes from a subscription.", IsImplemented = true, IsSharable = false, Availability = RpcEndpoint.All & ~RpcEndpoint.Http)] diff --git a/src/Nethermind/Nethermind.Network.Stats/Model/Node.cs b/src/Nethermind/Nethermind.Network.Stats/Model/Node.cs index e777396ed54..75f3e4d5124 100644 --- a/src/Nethermind/Nethermind.Network.Stats/Model/Node.cs +++ b/src/Nethermind/Nethermind.Network.Stats/Model/Node.cs @@ -53,6 +53,9 @@ public sealed class Node : IFormattable, IEquatable /// public bool IsStatic { get; set; } + public bool IsTrusted { get; set; } + + public string ClientId { get => _clientId; diff --git a/src/Nethermind/Nethermind.Network.Test/PeerManagerTests.cs b/src/Nethermind/Nethermind.Network.Test/PeerManagerTests.cs index e5375d58669..b47cc71da6b 100644 --- a/src/Nethermind/Nethermind.Network.Test/PeerManagerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/PeerManagerTests.cs @@ -648,7 +648,8 @@ public Context(int parallelism = 0, int maxActivePeers = 25) StaticNodesManager.DiscoverNodes(Arg.Any()).Returns(AsyncEnumerable.Empty()); TestNodeSource = new TestNodeSource(); CompositeNodeSource nodeSources = new(NodesLoader, DiscoveryApp, StaticNodesManager, TestNodeSource); - PeerPool = new PeerPool(nodeSources, Stats, Storage, NetworkConfig, LimboLogs.Instance); + ITrustedNodesManager trustedNodesManager = Substitute.For(); + PeerPool = new PeerPool(nodeSources, Stats, Storage, NetworkConfig, LimboLogs.Instance, trustedNodesManager); CreatePeerManager(); } diff --git a/src/Nethermind/Nethermind.Network.Test/PeerPoolTests.cs b/src/Nethermind/Nethermind.Network.Test/PeerPoolTests.cs index a30f0a9db48..92e13cb55d6 100644 --- a/src/Nethermind/Nethermind.Network.Test/PeerPoolTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/PeerPoolTests.cs @@ -20,6 +20,8 @@ public class PeerPoolTests [Test] public async Task PeerPool_ShouldThrottleSource_WhenFull() { + var trustedNodesManager = Substitute.For(); + TestNodeSource nodeSource = new TestNodeSource(); PeerPool pool = new PeerPool( nodeSource, @@ -30,7 +32,8 @@ public async Task PeerPool_ShouldThrottleSource_WhenFull() MaxActivePeers = 5, MaxCandidatePeerCount = 10 }, - LimboLogs.Instance); + LimboLogs.Instance, + trustedNodesManager); Random rand = new Random(0); PrivateKeyGenerator keyGen = new PrivateKeyGenerator(new TestRandom((m) => rand.Next(m), (s) => diff --git a/src/Nethermind/Nethermind.Network/PeerPool.cs b/src/Nethermind/Nethermind.Network/PeerPool.cs index bfb0177d48d..668011500b9 100644 --- a/src/Nethermind/Nethermind.Network/PeerPool.cs +++ b/src/Nethermind/Nethermind.Network/PeerPool.cs @@ -29,6 +29,7 @@ public class PeerPool : IPeerPool private readonly INetworkStorage _peerStorage; private readonly INetworkConfig _networkConfig; private readonly ILogger _logger; + private readonly ITrustedNodesManager _trustedNodesManager; public ConcurrentDictionary ActivePeers { get; } = new(); public ConcurrentDictionary Peers { get; } = new(); @@ -51,7 +52,9 @@ public PeerPool( INodeStatsManager nodeStatsManager, [KeyFilter(INetworkStorage.PeerDb)] INetworkStorage peerStorage, INetworkConfig networkConfig, - ILogManager logManager) + ILogManager logManager, + ITrustedNodesManager trustedNodesManager) + { _nodeSource = nodeSource ?? throw new ArgumentNullException(nameof(nodeSource)); _stats = nodeStatsManager ?? throw new ArgumentNullException(nameof(nodeStatsManager)); @@ -59,6 +62,7 @@ public PeerPool( _networkConfig = networkConfig ?? throw new ArgumentNullException(nameof(networkConfig)); _peerStorage.StartBatch(); _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); + _trustedNodesManager = trustedNodesManager ?? throw new ArgumentNullException(nameof(trustedNodesManager)); // Early explicit closure _createNewNodePeer = CreateNew; @@ -84,6 +88,13 @@ public Peer GetOrAdd(NetworkNode node) private Peer CreateNew(PublicKeyAsKey key, (Node Node, ConcurrentDictionary Statics) arg) { + string enodeString = arg.Node.ToString(Node.Format.ENode); + + if (_trustedNodesManager.IsTrusted(enodeString)) + { + arg.Node.IsTrusted = true; + } + if (arg.Node.IsBootnode || arg.Node.IsStatic) { if (_logger.IsDebug) _logger.Debug( @@ -102,6 +113,15 @@ private Peer CreateNew(PublicKeyAsKey key, (Node Node, ConcurrentDictionary Statics) arg) { Node node = new(arg.Node); + + string enodeString = node.ToString(Node.Format.ENode); + + // Check if this node is trusted + if (_trustedNodesManager.IsTrusted(enodeString)) + { + node.IsTrusted = true; + } + Peer peer = new(node, _stats.GetOrAdd(node)); PeerAdded?.Invoke(this, new PeerEventArgs(peer)); diff --git a/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs b/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs new file mode 100644 index 00000000000..717994af01c --- /dev/null +++ b/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs @@ -0,0 +1,175 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Text.Json; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; +using Nethermind.Config; +using Nethermind.Core.Crypto; +using Nethermind.Logging; +using Nethermind.Network.StaticNodes; +using Nethermind.Serialization.Json; +using Nethermind.Stats.Model; + +namespace Nethermind.Network +{ + public class TrustedNodesManager : ITrustedNodesManager + { + private ConcurrentDictionary _nodes = new(); + + private readonly string _trustedNodesPath; + private readonly ILogger _logger; + + public TrustedNodesManager(string trustedNodesPath, ILogManager logManager) + { + _trustedNodesPath = trustedNodesPath.GetApplicationResourcePath(); + _logger = logManager.GetClassLogger(); + } + + public IEnumerable Nodes => _nodes.Values; + + private static readonly char[] separator = new[] { '\r', '\n' }; + + public async Task InitAsync() + { + if (!File.Exists(_trustedNodesPath)) + { + if (_logger.IsDebug) _logger.Debug($"Trusted nodes file not found for path: {_trustedNodesPath}"); + return; + } + + string data = await File.ReadAllTextAsync(_trustedNodesPath); + string[] nodes = GetNodes(data); + if (_logger.IsInfo) + _logger.Info($"Loaded {nodes.Length} trusted nodes from file: {Path.GetFullPath(_trustedNodesPath)}"); + if (nodes.Length != 0 && _logger.IsDebug) + { + _logger.Debug($"Trusted nodes: {Environment.NewLine}{data}"); + } + + List networkNodes = new(); + foreach (string? n in nodes) + { + try + { + NetworkNode networkNode = new(n); + networkNodes.Add(networkNode); + } + catch (Exception exception) when (exception is ArgumentException or SocketException) + { + if (_logger.IsError) _logger.Error("Unable to process node. ", exception); + } + } + + _nodes = new ConcurrentDictionary(networkNodes.ToDictionary(n => n.NodeId, n => n)); + } + + private static string[] GetNodes(string data) + { + string[] nodes; + try + { + nodes = JsonSerializer.Deserialize(data) ?? Array.Empty(); + } + catch (JsonException) + { + nodes = data.Split(separator, StringSplitOptions.RemoveEmptyEntries); + } + + return nodes.Distinct().ToArray(); + } + + public async Task AddAsync(string enode, bool updateFile = true) + { + NetworkNode networkNode = new(enode); + if (!_nodes.TryAdd(networkNode.NodeId, networkNode)) + { + if (_logger.IsInfo) _logger.Info($"Trusted node was already added: {enode}"); + return false; + } + + if (_logger.IsInfo) _logger.Info($"Trusted node added: {enode}"); + Node node = new(networkNode) { IsTrusted = true }; + NodeAdded?.Invoke(this, new NodeEventArgs(node)); + if (updateFile) + { + await SaveFileAsync(); + } + + return true; + } + + public async Task RemoveAsync(string enode, bool updateFile = true) + { + NetworkNode networkNode = new(enode); + if (!_nodes.TryRemove(networkNode.NodeId, out _)) + { + if (_logger.IsInfo) _logger.Info($"Trusted node was not found: {enode}"); + return false; + } + + if (_logger.IsInfo) _logger.Info($"Trusted node was removed: {enode}"); + Node node = new(networkNode) { IsTrusted = true }; + NodeRemoved?.Invoke(this, new NodeEventArgs(node)); + if (updateFile) + { + await SaveFileAsync(); + } + + return true; + } + + public bool IsTrusted(string enode) + { + NetworkNode node = new(enode); + return _nodes.ContainsKey(node.NodeId); + } + + private Task SaveFileAsync() + => File.WriteAllTextAsync(_trustedNodesPath, + JsonSerializer.Serialize(_nodes.Values.Select(n => n.ToString()), EthereumJsonSerializer.JsonOptionsIndented)); + + public async IAsyncEnumerable DiscoverNodes([EnumeratorCancellation] CancellationToken cancellationToken) + { + Channel ch = Channel.CreateBounded(128); + + foreach (Node node in _nodes.Values.Select(n => new Node(n) { IsTrusted = true })) + { + cancellationToken.ThrowIfCancellationRequested(); + yield return node; + } + + void handler(object? _, NodeEventArgs args) + { + ch.Writer.TryWrite(args.Node); + } + + try + { + NodeAdded += handler; + + await foreach (Node node in ch.Reader.ReadAllAsync(cancellationToken)) + { + yield return node; + } + } + finally + { + NodeAdded -= handler; + } + } + + private event EventHandler? NodeAdded; + public event EventHandler? NodeRemoved; + } +} diff --git a/src/Nethermind/Nethermind.Network/iTrustedNodesManager.cs b/src/Nethermind/Nethermind.Network/iTrustedNodesManager.cs new file mode 100644 index 00000000000..50747263f12 --- /dev/null +++ b/src/Nethermind/Nethermind.Network/iTrustedNodesManager.cs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Threading.Tasks; +using Nethermind.Config; + +namespace Nethermind.Network +{ + public interface ITrustedNodesManager : INodeSource + { + IEnumerable Nodes { get; } + Task InitAsync(); + Task AddAsync(string enode, bool updateFile = true); + Task RemoveAsync(string enode, bool updateFile = true); + bool IsTrusted(string enode); + } +} From 208298519594a046127a5db6b163e154dff6ac9b Mon Sep 17 00:00:00 2001 From: Richard Gregory Date: Wed, 11 Dec 2024 09:16:13 +0100 Subject: [PATCH 02/15] Add test for admin_addTrustedPeer --- .../Modules/AdminModuleTests.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs index 5d3fba80924..7136a0921c5 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs @@ -299,6 +299,46 @@ public async Task Test_hasStateForBlock() (await RpcTest.TestSerializedRequest(_adminRpcModule, "admin_isStateRootAvailable", "latest")).Should().Contain("true"); } + [Test] + public async Task Test_admin_addTrustedPeer() + { + // trustedNodesManager and peerPool mocks initialized in Setup() + // We'll configure the trustedNodesManager mock to return true when adding a trusted node. + + ITrustedNodesManager trustedNodesManager = Substitute.For(); + trustedNodesManager.AddAsync(_enodeString, true).Returns(Task.FromResult(true)); + + IPeerPool peerPool = Substitute.For(); + NetworkNode node = new(_enodeString); + + // The AdminRpcModule requires the full constructor call with ITrustedNodesManager: + ChainSpec chainSpec = new() { Parameters = new ChainParameters() }; + IAdminRpcModule adminRpcModule = new AdminRpcModule( + _blockTree, + _networkConfig, + peerPool, + Substitute.For(), + new Enode(_enodeString), + _exampleDataDir, + new ManualPruningTrigger(), + chainSpec.Parameters, + trustedNodesManager); + + // Invoke admin_addTrustedPeer + string serialized = await RpcTest.TestSerializedRequest(adminRpcModule, "admin_addTrustedPeer", _enodeString); + + // Check the response to ensure it succeeded + JsonRpcSuccessResponse response = _serializer.Deserialize(serialized); + bool result = ((JsonElement)response.Result!).Deserialize(EthereumJsonSerializer.JsonOptions); + result.Should().BeTrue(); + + // Verify that AddAsync was called + await trustedNodesManager.Received(1).AddAsync(_enodeString, Arg.Any()); + + // Verify that peerPool.GetOrAdd was called for that node + peerPool.ReceivedWithAnyArgs(1).GetOrAdd(node); + } + [Test] public async Task Smoke_solc() { From 06e94c1a86f8f5d0625b28aa914641360eba46a8 Mon Sep 17 00:00:00 2001 From: Richard Gregory Date: Fri, 20 Dec 2024 17:48:45 +0100 Subject: [PATCH 03/15] Add review notes to admin_addTrustedPeer implementation --- .../Modules/AdminModuleTests.cs | 29 ++++++++++--------- .../Modules/Admin/AdminRpcModule.cs | 6 ++-- src/Nethermind/Nethermind.Network/PeerPool.cs | 11 ++----- .../TrustedNodes/TrustedNodesManager.cs | 28 ++++++++++++------ .../iTrustedNodesManager.cs | 6 ++-- 5 files changed, 44 insertions(+), 36 deletions(-) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs index 7136a0921c5..d2e8237c254 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs @@ -302,17 +302,17 @@ public async Task Test_hasStateForBlock() [Test] public async Task Test_admin_addTrustedPeer() { - // trustedNodesManager and peerPool mocks initialized in Setup() - // We'll configure the trustedNodesManager mock to return true when adding a trusted node. - + // Setup dependencies ITrustedNodesManager trustedNodesManager = Substitute.For(); - trustedNodesManager.AddAsync(_enodeString, true).Returns(Task.FromResult(true)); - IPeerPool peerPool = Substitute.For(); - NetworkNode node = new(_enodeString); - // The AdminRpcModule requires the full constructor call with ITrustedNodesManager: ChainSpec chainSpec = new() { Parameters = new ChainParameters() }; + Enode testEnode = new(_enodeString); + + // Mock AddAsync to return true for any enode (to simplify) + trustedNodesManager.AddAsync(Arg.Any(), Arg.Any()).Returns(Task.FromResult(true)); + + // Create the adminRpcModule as IAdminRpcModule (important for RpcTest) IAdminRpcModule adminRpcModule = new AdminRpcModule( _blockTree, _networkConfig, @@ -324,19 +324,20 @@ public async Task Test_admin_addTrustedPeer() chainSpec.Parameters, trustedNodesManager); - // Invoke admin_addTrustedPeer + // Call admin_addTrustedPeer via the RPC test helper string serialized = await RpcTest.TestSerializedRequest(adminRpcModule, "admin_addTrustedPeer", _enodeString); - // Check the response to ensure it succeeded + // Deserialize the response JsonRpcSuccessResponse response = _serializer.Deserialize(serialized); + response.Should().NotBeNull("Response should not be null"); bool result = ((JsonElement)response.Result!).Deserialize(EthereumJsonSerializer.JsonOptions); - result.Should().BeTrue(); + result.Should().BeTrue("The RPC call should succeed and return true"); - // Verify that AddAsync was called - await trustedNodesManager.Received(1).AddAsync(_enodeString, Arg.Any()); + // Verify that AddAsync was called once with any Enode + await trustedNodesManager.Received(1).AddAsync(Arg.Any(), Arg.Any()); - // Verify that peerPool.GetOrAdd was called for that node - peerPool.ReceivedWithAnyArgs(1).GetOrAdd(node); + // Verify that peerPool.GetOrAdd was called once with any NetworkNode + peerPool.Received(1).GetOrAdd(Arg.Any()); } [Test] diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs index 3b41de5e6f0..5357f01d4ff 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs @@ -137,10 +137,12 @@ public async Task> admin_removePeer(string enode, bool rem public async Task> admin_addTrustedPeer(string enode) { - bool added = await _trustedNodesManager.AddAsync(enode); + Enode enodeObj = new(enode); + bool added = await _trustedNodesManager.AddAsync(enodeObj); + if (added) { - _peerPool.GetOrAdd(new NetworkNode(enode)); + _peerPool.GetOrAdd(new NetworkNode(enodeObj.ToString())); return ResultWrapper.Success(true); } diff --git a/src/Nethermind/Nethermind.Network/PeerPool.cs b/src/Nethermind/Nethermind.Network/PeerPool.cs index 668011500b9..0e32b33e66c 100644 --- a/src/Nethermind/Nethermind.Network/PeerPool.cs +++ b/src/Nethermind/Nethermind.Network/PeerPool.cs @@ -88,13 +88,6 @@ public Peer GetOrAdd(NetworkNode node) private Peer CreateNew(PublicKeyAsKey key, (Node Node, ConcurrentDictionary Statics) arg) { - string enodeString = arg.Node.ToString(Node.Format.ENode); - - if (_trustedNodesManager.IsTrusted(enodeString)) - { - arg.Node.IsTrusted = true; - } - if (arg.Node.IsBootnode || arg.Node.IsStatic) { if (_logger.IsDebug) _logger.Debug( @@ -116,8 +109,10 @@ private Peer CreateNew(PublicKeyAsKey key, (NetworkNode Node, ConcurrentDictiona string enodeString = node.ToString(Node.Format.ENode); + Enode enode = new Enode(enodeString); + // Check if this node is trusted - if (_trustedNodesManager.IsTrusted(enodeString)) + if (_trustedNodesManager.IsTrusted(enode)) { node.IsTrusted = true; } diff --git a/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs b/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs index 717994af01c..620b3276205 100644 --- a/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs +++ b/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs @@ -47,14 +47,24 @@ public async Task InitAsync() if (_logger.IsDebug) _logger.Debug($"Trusted nodes file not found for path: {_trustedNodesPath}"); return; } + List lines = new(); + await foreach (string line in File.ReadLinesAsync(_trustedNodesPath)) + { + if (!string.IsNullOrWhiteSpace(line)) + { + lines.Add(line); + } + } + + string[] nodes = lines.Distinct().ToArray(); - string data = await File.ReadAllTextAsync(_trustedNodesPath); - string[] nodes = GetNodes(data); if (_logger.IsInfo) _logger.Info($"Loaded {nodes.Length} trusted nodes from file: {Path.GetFullPath(_trustedNodesPath)}"); + if (nodes.Length != 0 && _logger.IsDebug) { - _logger.Debug($"Trusted nodes: {Environment.NewLine}{data}"); + string formattedNodes = string.Join(Environment.NewLine, nodes); + _logger.Debug($"Trusted nodes: {Environment.NewLine}{formattedNodes}"); } List networkNodes = new(); @@ -89,9 +99,9 @@ private static string[] GetNodes(string data) return nodes.Distinct().ToArray(); } - public async Task AddAsync(string enode, bool updateFile = true) + public async Task AddAsync(Enode enode, bool updateFile = true) { - NetworkNode networkNode = new(enode); + NetworkNode networkNode = new(enode.ToString()); if (!_nodes.TryAdd(networkNode.NodeId, networkNode)) { if (_logger.IsInfo) _logger.Info($"Trusted node was already added: {enode}"); @@ -109,9 +119,9 @@ public async Task AddAsync(string enode, bool updateFile = true) return true; } - public async Task RemoveAsync(string enode, bool updateFile = true) + public async Task RemoveAsync(Enode enode, bool updateFile = true) { - NetworkNode networkNode = new(enode); + NetworkNode networkNode = new(enode.ToString()); if (!_nodes.TryRemove(networkNode.NodeId, out _)) { if (_logger.IsInfo) _logger.Info($"Trusted node was not found: {enode}"); @@ -129,9 +139,9 @@ public async Task RemoveAsync(string enode, bool updateFile = true) return true; } - public bool IsTrusted(string enode) + public bool IsTrusted(Enode enode) { - NetworkNode node = new(enode); + NetworkNode node = new(enode.ToString()); return _nodes.ContainsKey(node.NodeId); } diff --git a/src/Nethermind/Nethermind.Network/iTrustedNodesManager.cs b/src/Nethermind/Nethermind.Network/iTrustedNodesManager.cs index 50747263f12..64b71a6ec9d 100644 --- a/src/Nethermind/Nethermind.Network/iTrustedNodesManager.cs +++ b/src/Nethermind/Nethermind.Network/iTrustedNodesManager.cs @@ -11,8 +11,8 @@ public interface ITrustedNodesManager : INodeSource { IEnumerable Nodes { get; } Task InitAsync(); - Task AddAsync(string enode, bool updateFile = true); - Task RemoveAsync(string enode, bool updateFile = true); - bool IsTrusted(string enode); + Task AddAsync(Enode enode, bool updateFile = true); + Task RemoveAsync(Enode enode, bool updateFile = true); + bool IsTrusted(Enode enode); } } From 5d2a751e3326bddfef50d7b2ee66d6b1308b9a93 Mon Sep 17 00:00:00 2001 From: Richard Gregory Date: Sat, 21 Dec 2024 09:18:55 +0100 Subject: [PATCH 04/15] Temporarily rename file to change case --- .../Nethermind.Network/{iTrustedNodesManager.cs => tempname} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Nethermind/Nethermind.Network/{iTrustedNodesManager.cs => tempname} (100%) diff --git a/src/Nethermind/Nethermind.Network/iTrustedNodesManager.cs b/src/Nethermind/Nethermind.Network/tempname similarity index 100% rename from src/Nethermind/Nethermind.Network/iTrustedNodesManager.cs rename to src/Nethermind/Nethermind.Network/tempname From be0e6ebcbe890ebfacd0faa210f7e9cb919a8784 Mon Sep 17 00:00:00 2001 From: Richard Gregory Date: Sat, 21 Dec 2024 09:20:28 +0100 Subject: [PATCH 05/15] Rename file to ITrustedNodesManager with correct case --- .../Nethermind.Network/{tempname => ITrustedNodesManager.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Nethermind/Nethermind.Network/{tempname => ITrustedNodesManager.cs} (100%) diff --git a/src/Nethermind/Nethermind.Network/tempname b/src/Nethermind/Nethermind.Network/ITrustedNodesManager.cs similarity index 100% rename from src/Nethermind/Nethermind.Network/tempname rename to src/Nethermind/Nethermind.Network/ITrustedNodesManager.cs From 7a0e6c548c41cd8b94b96d0991bc641289dd1c03 Mon Sep 17 00:00:00 2001 From: Richard Gregory Date: Wed, 1 Jan 2025 17:23:29 +0100 Subject: [PATCH 06/15] Remove unneeded channel in TrustedNodesManager --- .../TrustedNodes/TrustedNodesManager.cs | 135 ++++++++---------- 1 file changed, 61 insertions(+), 74 deletions(-) diff --git a/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs b/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs index 620b3276205..549dc5e8200 100644 --- a/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs +++ b/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -// SPDX-License-Identifier: LGPL-3.0-only - using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -12,13 +10,10 @@ using System.Runtime.CompilerServices; using System.Text.Json; using System.Threading; -using System.Threading.Channels; using System.Threading.Tasks; using Nethermind.Config; using Nethermind.Core.Crypto; using Nethermind.Logging; -using Nethermind.Network.StaticNodes; -using Nethermind.Serialization.Json; using Nethermind.Stats.Model; namespace Nethermind.Network @@ -26,7 +21,6 @@ namespace Nethermind.Network public class TrustedNodesManager : ITrustedNodesManager { private ConcurrentDictionary _nodes = new(); - private readonly string _trustedNodesPath; private readonly ILogger _logger; @@ -38,16 +32,19 @@ public TrustedNodesManager(string trustedNodesPath, ILogManager logManager) public IEnumerable Nodes => _nodes.Values; - private static readonly char[] separator = new[] { '\r', '\n' }; - public async Task InitAsync() { if (!File.Exists(_trustedNodesPath)) { - if (_logger.IsDebug) _logger.Debug($"Trusted nodes file not found for path: {_trustedNodesPath}"); + if (_logger.IsDebug) + { + _logger.Debug($"Trusted nodes file not found at: {_trustedNodesPath}"); + } return; } - List lines = new(); + + HashSet lines = new(); + // Read lines asynchronously await foreach (string line in File.ReadLinesAsync(_trustedNodesPath)) { if (!string.IsNullOrWhiteSpace(line)) @@ -56,47 +53,50 @@ public async Task InitAsync() } } - string[] nodes = lines.Distinct().ToArray(); - if (_logger.IsInfo) - _logger.Info($"Loaded {nodes.Length} trusted nodes from file: {Path.GetFullPath(_trustedNodesPath)}"); + { + _logger.Info($"Loaded {lines.Count} trusted nodes from: {Path.GetFullPath(_trustedNodesPath)}"); + } - if (nodes.Length != 0 && _logger.IsDebug) + if (lines.Any() && _logger.IsDebug) { - string formattedNodes = string.Join(Environment.NewLine, nodes); - _logger.Debug($"Trusted nodes: {Environment.NewLine}{formattedNodes}"); + _logger.Debug("Trusted nodes:\n" + string.Join(Environment.NewLine, lines)); } + // Parse each line into a NetworkNode List networkNodes = new(); - foreach (string? n in nodes) + foreach (string line in lines) { try { - NetworkNode networkNode = new(n); - networkNodes.Add(networkNode); + networkNodes.Add(new NetworkNode(line)); } - catch (Exception exception) when (exception is ArgumentException or SocketException) + catch (Exception ex) when (ex is ArgumentException or SocketException) { - if (_logger.IsError) _logger.Error("Unable to process node. ", exception); + if (_logger.IsError) + { + _logger.Error($"Failed to parse '{line}' as a trusted node.", ex); + } } } - _nodes = new ConcurrentDictionary(networkNodes.ToDictionary(n => n.NodeId, n => n)); + _nodes = new ConcurrentDictionary( + networkNodes.ToDictionary(n => n.NodeId, n => n)); } - private static string[] GetNodes(string data) + // ---- INodeSource requirement: IAsyncEnumerable ---- + // C# requires 'async' for IAsyncEnumerable yield. We'll add a small 'await Task.Yield()' + // to avoid the warning about "no awaits". + public async IAsyncEnumerable DiscoverNodes([EnumeratorCancellation] CancellationToken cancellationToken) { - string[] nodes; - try - { - nodes = JsonSerializer.Deserialize(data) ?? Array.Empty(); - } - catch (JsonException) + // At least one 'await', so no compiler warnings + await Task.Yield(); + + foreach (NetworkNode netNode in _nodes.Values) { - nodes = data.Split(separator, StringSplitOptions.RemoveEmptyEntries); + cancellationToken.ThrowIfCancellationRequested(); + yield return new Node(netNode) { IsTrusted = true }; } - - return nodes.Distinct().ToArray(); } public async Task AddAsync(Enode enode, bool updateFile = true) @@ -104,18 +104,22 @@ public async Task AddAsync(Enode enode, bool updateFile = true) NetworkNode networkNode = new(enode.ToString()); if (!_nodes.TryAdd(networkNode.NodeId, networkNode)) { - if (_logger.IsInfo) _logger.Info($"Trusted node was already added: {enode}"); + if (_logger.IsInfo) + { + _logger.Info($"Trusted node was already added: {enode}"); + } return false; } - if (_logger.IsInfo) _logger.Info($"Trusted node added: {enode}"); - Node node = new(networkNode) { IsTrusted = true }; - NodeAdded?.Invoke(this, new NodeEventArgs(node)); + if (_logger.IsInfo) + { + _logger.Info($"Trusted node added: {enode}"); + } + if (updateFile) { await SaveFileAsync(); } - return true; } @@ -124,18 +128,22 @@ public async Task RemoveAsync(Enode enode, bool updateFile = true) NetworkNode networkNode = new(enode.ToString()); if (!_nodes.TryRemove(networkNode.NodeId, out _)) { - if (_logger.IsInfo) _logger.Info($"Trusted node was not found: {enode}"); + if (_logger.IsInfo) + { + _logger.Info($"Trusted node was not found: {enode}"); + } return false; } - if (_logger.IsInfo) _logger.Info($"Trusted node was removed: {enode}"); - Node node = new(networkNode) { IsTrusted = true }; - NodeRemoved?.Invoke(this, new NodeEventArgs(node)); + if (_logger.IsInfo) + { + _logger.Info($"Trusted node was removed: {enode}"); + } + if (updateFile) { await SaveFileAsync(); } - return true; } @@ -145,41 +153,20 @@ public bool IsTrusted(Enode enode) return _nodes.ContainsKey(node.NodeId); } - private Task SaveFileAsync() - => File.WriteAllTextAsync(_trustedNodesPath, - JsonSerializer.Serialize(_nodes.Values.Select(n => n.ToString()), EthereumJsonSerializer.JsonOptionsIndented)); - - public async IAsyncEnumerable DiscoverNodes([EnumeratorCancellation] CancellationToken cancellationToken) + // ---- INodeSource requirement: event EventHandler ---- + public event EventHandler? NodeRemoved { - Channel ch = Channel.CreateBounded(128); - - foreach (Node node in _nodes.Values.Select(n => new Node(n) { IsTrusted = true })) - { - cancellationToken.ThrowIfCancellationRequested(); - yield return node; - } - - void handler(object? _, NodeEventArgs args) - { - ch.Writer.TryWrite(args.Node); - } + add { /* no-op */ } + remove { /* no-op*/ } + } - try - { - NodeAdded += handler; - await foreach (Node node in ch.Reader.ReadAllAsync(cancellationToken)) - { - yield return node; - } - } - finally - { - NodeAdded -= handler; - } + private Task SaveFileAsync() + { + // Serialize the Enode strings from each stored node + IEnumerable enodes = _nodes.Values.Select(n => n.ToString()); + return File.WriteAllTextAsync(_trustedNodesPath, + JsonSerializer.Serialize(enodes, new JsonSerializerOptions { WriteIndented = true })); } - - private event EventHandler? NodeAdded; - public event EventHandler? NodeRemoved; } } From 5d11de3cb850e231554735df4a92dabb576ceeea Mon Sep 17 00:00:00 2001 From: Richard Gregory Date: Fri, 10 Jan 2025 11:18:18 +0100 Subject: [PATCH 07/15] Add latest parameters to Test_admin_addTrustedPeer --- .../Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs index d2e8237c254..32f1eefe1b1 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs @@ -318,6 +318,8 @@ public async Task Test_admin_addTrustedPeer() _networkConfig, peerPool, Substitute.For(), + Substitute.For(), + Substitute.For(), new Enode(_enodeString), _exampleDataDir, new ManualPruningTrigger(), From 23dcf7b15511dc81cd02b028f48395cf0260efae Mon Sep 17 00:00:00 2001 From: Richard Gregory Date: Sun, 2 Feb 2025 14:24:49 +0100 Subject: [PATCH 08/15] Config trusted nodes manager to open file as stream and serialize directly into file --- .../Nethermind.Network/ITrustedNodesManager.cs | 6 +++--- .../TrustedNodes/TrustedNodesManager.cs | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Nethermind/Nethermind.Network/ITrustedNodesManager.cs b/src/Nethermind/Nethermind.Network/ITrustedNodesManager.cs index 64b71a6ec9d..bb8f909f0b4 100644 --- a/src/Nethermind/Nethermind.Network/ITrustedNodesManager.cs +++ b/src/Nethermind/Nethermind.Network/ITrustedNodesManager.cs @@ -11,8 +11,8 @@ public interface ITrustedNodesManager : INodeSource { IEnumerable Nodes { get; } Task InitAsync(); - Task AddAsync(Enode enode, bool updateFile = true); - Task RemoveAsync(Enode enode, bool updateFile = true); - bool IsTrusted(Enode enode); + Task AddAsync(Enode enode, bool updateFile = true); + Task RemoveAsync(Enode enode, bool updateFile = true); + bool IsTrusted(Enode enode); } } diff --git a/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs b/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs index 549dc5e8200..72ddd0db161 100644 --- a/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs +++ b/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs @@ -161,12 +161,13 @@ public event EventHandler? NodeRemoved } - private Task SaveFileAsync() + private async Task SaveFileAsync() { - // Serialize the Enode strings from each stored node IEnumerable enodes = _nodes.Values.Select(n => n.ToString()); - return File.WriteAllTextAsync(_trustedNodesPath, - JsonSerializer.Serialize(enodes, new JsonSerializerOptions { WriteIndented = true })); + using (FileStream stream = File.Create(_trustedNodesPath)) + { + await JsonSerializer.SerializeAsync(stream, enodes, new JsonSerializerOptions { WriteIndented = true }); + } } } } From b9f5038b8732afb4266111ce9c15ef5e4a08f512 Mon Sep 17 00:00:00 2001 From: Richard Gregory Date: Sun, 2 Feb 2025 14:45:45 +0100 Subject: [PATCH 09/15] Remove unnecessary intermediate collections in InitAsync --- .../TrustedNodes/TrustedNodesManager.cs | 40 ++++++++----------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs b/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs index 72ddd0db161..8ba22eab2eb 100644 --- a/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs +++ b/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs @@ -43,33 +43,18 @@ public async Task InitAsync() return; } - HashSet lines = new(); - // Read lines asynchronously + var nodes = new ConcurrentDictionary(); + await foreach (string line in File.ReadLinesAsync(_trustedNodesPath)) { - if (!string.IsNullOrWhiteSpace(line)) + if (string.IsNullOrWhiteSpace(line)) { - lines.Add(line); + continue; } - } - - if (_logger.IsInfo) - { - _logger.Info($"Loaded {lines.Count} trusted nodes from: {Path.GetFullPath(_trustedNodesPath)}"); - } - - if (lines.Any() && _logger.IsDebug) - { - _logger.Debug("Trusted nodes:\n" + string.Join(Environment.NewLine, lines)); - } - - // Parse each line into a NetworkNode - List networkNodes = new(); - foreach (string line in lines) - { try { - networkNodes.Add(new NetworkNode(line)); + NetworkNode node = new NetworkNode(line); + nodes.TryAdd(node.NodeId, node); } catch (Exception ex) when (ex is ArgumentException or SocketException) { @@ -80,10 +65,19 @@ public async Task InitAsync() } } - _nodes = new ConcurrentDictionary( - networkNodes.ToDictionary(n => n.NodeId, n => n)); + if (_logger.IsInfo) + { + _logger.Info($"Loaded {nodes.Count} trusted nodes from: {Path.GetFullPath(_trustedNodesPath)}"); + } + if (nodes.Any() && _logger.IsDebug) + { + _logger.Debug("Trusted nodes:\n" + string.Join(Environment.NewLine, nodes.Values.Select(n => n.ToString()))); + } + + _nodes = nodes; } + // ---- INodeSource requirement: IAsyncEnumerable ---- // C# requires 'async' for IAsyncEnumerable yield. We'll add a small 'await Task.Yield()' // to avoid the warning about "no awaits". From f3abc67772592fd935862ba1c2b0ff352ee16960 Mon Sep 17 00:00:00 2001 From: Richard Gregory Date: Mon, 3 Feb 2025 14:13:47 +0100 Subject: [PATCH 10/15] Configure admin_addTrustedPeer to check other parts of Enode like Host and Port --- .../Nethermind.Config/NetworkNode.cs | 2 ++ .../Modules/Admin/AdminRpcModule.cs | 24 +++++++++++------ .../TrustedNodes/TrustedNodesManager.cs | 27 ++++++++++++++----- 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/src/Nethermind/Nethermind.Config/NetworkNode.cs b/src/Nethermind/Nethermind.Config/NetworkNode.cs index b85e1b0cbac..9ade0a80522 100644 --- a/src/Nethermind/Nethermind.Config/NetworkNode.cs +++ b/src/Nethermind/Nethermind.Config/NetworkNode.cs @@ -58,5 +58,7 @@ public NetworkNode(PublicKey publicKey, string ip, int port, long reputation = 0 public string Host => _enode.HostIp.ToString(); public int Port => _enode.Port; public long Reputation { get; set; } + + public NetworkNode(Enode enode) : this(enode.ToString()){} } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs index 5357f01d4ff..c7fc708d096 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs @@ -138,18 +138,26 @@ public async Task> admin_removePeer(string enode, bool rem public async Task> admin_addTrustedPeer(string enode) { Enode enodeObj = new(enode); - bool added = await _trustedNodesManager.AddAsync(enodeObj); - if (added) + bool added = await _trustedNodesManager.AddAsync(enodeObj, updateFile: true); + + + if (!added && _trustedNodesManager.IsTrusted(enodeObj)) { - _peerPool.GetOrAdd(new NetworkNode(enodeObj.ToString())); + // The node is already trusted—this is acceptable. + added = true; + } - return ResultWrapper.Success(true); - } + if (added) + { + // Add it to the peer pool so that the node is connectable. + _peerPool.GetOrAdd(new NetworkNode(enodeObj.ToString())); + return ResultWrapper.Success(true); + } else - { - return ResultWrapper.Fail("Failed to add trusted peer."); - } + { + return ResultWrapper.Fail("Failed to add trusted peer."); + } } diff --git a/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs b/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs index 8ba22eab2eb..e96f2be216a 100644 --- a/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs +++ b/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs @@ -95,7 +95,7 @@ public async IAsyncEnumerable DiscoverNodes([EnumeratorCancellation] Cance public async Task AddAsync(Enode enode, bool updateFile = true) { - NetworkNode networkNode = new(enode.ToString()); + NetworkNode networkNode = new(enode); if (!_nodes.TryAdd(networkNode.NodeId, networkNode)) { if (_logger.IsInfo) @@ -138,20 +138,35 @@ public async Task RemoveAsync(Enode enode, bool updateFile = true) { await SaveFileAsync(); } + + OnNodeRemoved(networkNode); + return true; } public bool IsTrusted(Enode enode) { - NetworkNode node = new(enode.ToString()); - return _nodes.ContainsKey(node.NodeId); + if (enode.PublicKey is null) + { + return false; + } + if (_nodes.TryGetValue(enode.PublicKey, out NetworkNode storedNode)) + { + // Compare not only the public key, but also the host and port. + return storedNode.Host == enode.HostIp?.ToString() && storedNode.Port == enode.Port; + } + return false; } + + // ---- INodeSource requirement: event EventHandler ---- - public event EventHandler? NodeRemoved + public event EventHandler? NodeRemoved; + + private void OnNodeRemoved(NetworkNode node) { - add { /* no-op */ } - remove { /* no-op*/ } + Node nodeForEvent = new Node(node); + NodeRemoved?.Invoke(this, new NodeEventArgs(nodeForEvent)); } From ae08cd85806c22519060b5d6b721bc5e7e647d66 Mon Sep 17 00:00:00 2001 From: Richard Gregory Date: Wed, 5 Feb 2025 13:09:30 +0100 Subject: [PATCH 11/15] Expose Enode property and minor refactoring --- .../Nethermind.Config/NetworkNode.cs | 6 ++++- .../Modules/Admin/AdminRpcModule.cs | 24 ++++++------------- src/Nethermind/Nethermind.Network/PeerPool.cs | 12 +--------- .../TrustedNodes/TrustedNodesManager.cs | 8 ++----- 4 files changed, 15 insertions(+), 35 deletions(-) diff --git a/src/Nethermind/Nethermind.Config/NetworkNode.cs b/src/Nethermind/Nethermind.Config/NetworkNode.cs index 9ade0a80522..74f84a8c9cc 100644 --- a/src/Nethermind/Nethermind.Config/NetworkNode.cs +++ b/src/Nethermind/Nethermind.Config/NetworkNode.cs @@ -58,7 +58,11 @@ public NetworkNode(PublicKey publicKey, string ip, int port, long reputation = 0 public string Host => _enode.HostIp.ToString(); public int Port => _enode.Port; public long Reputation { get; set; } + public NetworkNode(Enode enode) + { + _enode = enode; + } - public NetworkNode(Enode enode) : this(enode.ToString()){} + public Enode Enode => _enode; } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs index c7fc708d096..9add2bda983 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs @@ -139,25 +139,15 @@ public async Task> admin_addTrustedPeer(string enode) { Enode enodeObj = new(enode); - bool added = await _trustedNodesManager.AddAsync(enodeObj, updateFile: true); - - - if (!added && _trustedNodesManager.IsTrusted(enodeObj)) + if (_trustedNodesManager.IsTrusted(enodeObj) || await _trustedNodesManager.AddAsync(enodeObj, updateFile: true)) { - // The node is already trusted—this is acceptable. - added = true; - } - - if (added) - { - // Add it to the peer pool so that the node is connectable. - _peerPool.GetOrAdd(new NetworkNode(enodeObj.ToString())); - return ResultWrapper.Success(true); - } + _peerPool.GetOrAdd(new NetworkNode(enodeObj)); + return ResultWrapper.Success(true); + } else - { - return ResultWrapper.Fail("Failed to add trusted peer."); - } + { + return ResultWrapper.Fail("Failed to add trusted peer."); + } } diff --git a/src/Nethermind/Nethermind.Network/PeerPool.cs b/src/Nethermind/Nethermind.Network/PeerPool.cs index 0e32b33e66c..439598d4957 100644 --- a/src/Nethermind/Nethermind.Network/PeerPool.cs +++ b/src/Nethermind/Nethermind.Network/PeerPool.cs @@ -105,17 +105,7 @@ private Peer CreateNew(PublicKeyAsKey key, (Node Node, ConcurrentDictionary Statics) arg) { - Node node = new(arg.Node); - - string enodeString = node.ToString(Node.Format.ENode); - - Enode enode = new Enode(enodeString); - - // Check if this node is trusted - if (_trustedNodesManager.IsTrusted(enode)) - { - node.IsTrusted = true; - } + Node node = new(arg.Node) { IsTrusted = _trustedNodesManager.IsTrusted(arg.Node.Enode) }; Peer peer = new(node, _stats.GetOrAdd(node)); diff --git a/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs b/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs index e96f2be216a..3f85c134e54 100644 --- a/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs +++ b/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs @@ -36,11 +36,7 @@ public async Task InitAsync() { if (!File.Exists(_trustedNodesPath)) { - if (_logger.IsDebug) - { - _logger.Debug($"Trusted nodes file not found at: {_trustedNodesPath}"); - } - return; + if (_logger.IsDebug) _logger.Debug($"Trusted nodes file not found at: {_trustedNodesPath}"); } var nodes = new ConcurrentDictionary(); @@ -69,7 +65,7 @@ public async Task InitAsync() { _logger.Info($"Loaded {nodes.Count} trusted nodes from: {Path.GetFullPath(_trustedNodesPath)}"); } - if (nodes.Any() && _logger.IsDebug) + if (_logger.IsDebug && nodes.Any()) { _logger.Debug("Trusted nodes:\n" + string.Join(Environment.NewLine, nodes.Values.Select(n => n.ToString()))); } From 0506234a9a20d97cd4d169082aa3b64e390725b6 Mon Sep 17 00:00:00 2001 From: Richard Gregory Date: Thu, 6 Feb 2025 09:13:22 +0100 Subject: [PATCH 12/15] Update Test_admin_addTrustedPeer to accept IVerifyTrieStarter and IAdminEraService --- .../Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs index 32f1eefe1b1..474ee2154f7 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs @@ -318,9 +318,10 @@ public async Task Test_admin_addTrustedPeer() _networkConfig, peerPool, Substitute.For(), - Substitute.For(), + Substitute.For(), Substitute.For(), new Enode(_enodeString), + Substitute.For(), _exampleDataDir, new ManualPruningTrigger(), chainSpec.Parameters, From 1b4c4f49e97361e23a4053574c2e02128b110da0 Mon Sep 17 00:00:00 2001 From: Richard Gregory Date: Thu, 6 Feb 2025 22:26:18 +0100 Subject: [PATCH 13/15] Add a channel to node discovery --- .../Nethermind.Config/NetworkNode.cs | 11 ++++---- .../Modules/Admin/AdminRpcModule.cs | 6 ++-- .../TrustedNodes/TrustedNodesManager.cs | 28 +++++++++++++++---- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/Nethermind/Nethermind.Config/NetworkNode.cs b/src/Nethermind/Nethermind.Config/NetworkNode.cs index 74f84a8c9cc..b3a3a9a5c80 100644 --- a/src/Nethermind/Nethermind.Config/NetworkNode.cs +++ b/src/Nethermind/Nethermind.Config/NetworkNode.cs @@ -54,15 +54,16 @@ public NetworkNode(PublicKey publicKey, string ip, int port, long reputation = 0 Reputation = reputation; } - public PublicKey NodeId => _enode.PublicKey; - public string Host => _enode.HostIp.ToString(); - public int Port => _enode.Port; - public long Reputation { get; set; } public NetworkNode(Enode enode) { - _enode = enode; + _enode = enode; } public Enode Enode => _enode; + + public PublicKey NodeId => _enode.PublicKey; + public string Host => _enode.HostIp.ToString(); + public int Port => _enode.Port; + public long Reputation { get; set; } } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs index 9add2bda983..8f988c768f4 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs @@ -141,12 +141,12 @@ public async Task> admin_addTrustedPeer(string enode) if (_trustedNodesManager.IsTrusted(enodeObj) || await _trustedNodesManager.AddAsync(enodeObj, updateFile: true)) { - _peerPool.GetOrAdd(new NetworkNode(enodeObj)); - return ResultWrapper.Success(true); + _peerPool.GetOrAdd(new NetworkNode(enodeObj)); + return ResultWrapper.Success(true); } else { - return ResultWrapper.Fail("Failed to add trusted peer."); + return ResultWrapper.Fail("Failed to add trusted peer."); } } diff --git a/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs b/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs index 3f85c134e54..6a71efd2088 100644 --- a/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs +++ b/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs @@ -10,6 +10,7 @@ using System.Runtime.CompilerServices; using System.Text.Json; using System.Threading; +using System.Threading.Channels; using System.Threading.Tasks; using Nethermind.Config; using Nethermind.Core.Crypto; @@ -23,6 +24,12 @@ public class TrustedNodesManager : ITrustedNodesManager private ConcurrentDictionary _nodes = new(); private readonly string _trustedNodesPath; private readonly ILogger _logger; + private readonly Channel _nodeChannel = Channel.CreateBounded( + new BoundedChannelOptions(1 << 16) // capacity of 2^16 = 65536 + { + // "Wait" to have writers wait until there is space. + FullMode = BoundedChannelFullMode.Wait + }); public TrustedNodesManager(string trustedNodesPath, ILogManager logManager) { @@ -75,23 +82,28 @@ public async Task InitAsync() // ---- INodeSource requirement: IAsyncEnumerable ---- - // C# requires 'async' for IAsyncEnumerable yield. We'll add a small 'await Task.Yield()' - // to avoid the warning about "no awaits". public async IAsyncEnumerable DiscoverNodes([EnumeratorCancellation] CancellationToken cancellationToken) { - // At least one 'await', so no compiler warnings - await Task.Yield(); - + // yield existing nodes. foreach (NetworkNode netNode in _nodes.Values) { cancellationToken.ThrowIfCancellationRequested(); yield return new Node(netNode) { IsTrusted = true }; } + + // continuously yield new nodes as they are added via the channel. + while (await _nodeChannel.Reader.WaitToReadAsync(cancellationToken)) + { + while (_nodeChannel.Reader.TryRead(out Node node)) + { + yield return node; + } + } } public async Task AddAsync(Enode enode, bool updateFile = true) { - NetworkNode networkNode = new(enode); + NetworkNode networkNode = new NetworkNode(enode); if (!_nodes.TryAdd(networkNode.NodeId, networkNode)) { if (_logger.IsInfo) @@ -106,6 +118,10 @@ public async Task AddAsync(Enode enode, bool updateFile = true) _logger.Info($"Trusted node added: {enode}"); } + // Publish the newly added node to the channel so DiscoverNodes will yield it. + Node newNode = new Node(networkNode) { IsTrusted = true }; + await _nodeChannel.Writer.WriteAsync(newNode); + if (updateFile) { await SaveFileAsync(); From 92988d679ebeb50dc1fa789be19abd3d0f63a4dd Mon Sep 17 00:00:00 2001 From: Richard Gregory Date: Wed, 12 Feb 2025 17:08:07 +0100 Subject: [PATCH 14/15] Fix failing Autofac and context with mocks test --- .../Nethermind.Core.Test/Modules/DiscoveryModule.cs | 3 +++ .../TrustedNodes/TrustedNodesManager.cs | 9 +++------ .../Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Nethermind/Nethermind.Core.Test/Modules/DiscoveryModule.cs b/src/Nethermind/Nethermind.Core.Test/Modules/DiscoveryModule.cs index b7525779bdd..373d092d51a 100644 --- a/src/Nethermind/Nethermind.Core.Test/Modules/DiscoveryModule.cs +++ b/src/Nethermind/Nethermind.Core.Test/Modules/DiscoveryModule.cs @@ -36,6 +36,9 @@ protected override void Load(ContainerBuilder builder) // This load from file. .AddSingleton() + .AddSingleton((logManager) => + new TrustedNodesManager(initConfig.TrustedNodesPath, logManager)) + .Bind() .Bind() .AddComposite() diff --git a/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs b/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs index 6a71efd2088..424ac40c94b 100644 --- a/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs +++ b/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs @@ -91,13 +91,10 @@ public async IAsyncEnumerable DiscoverNodes([EnumeratorCancellation] Cance yield return new Node(netNode) { IsTrusted = true }; } - // continuously yield new nodes as they are added via the channel. - while (await _nodeChannel.Reader.WaitToReadAsync(cancellationToken)) + // yield new nodes as they are added via the channel + await foreach (Node node in _nodeChannel.Reader.ReadAllAsync(cancellationToken)) { - while (_nodeChannel.Reader.TryRead(out Node node)) - { - yield return node; - } + yield return node; } } diff --git a/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs b/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs index 1f509568cc5..8d5f43e70b7 100644 --- a/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs +++ b/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs @@ -74,6 +74,7 @@ public static NethermindApi ContextWithMocks() RewardCalculatorSource = Substitute.For(), TxPoolInfoProvider = Substitute.For(), StaticNodesManager = Substitute.For(), + TrustedNodesManager = Substitute.For(), BloomStorage = Substitute.For(), Sealer = Substitute.For(), BlockchainProcessor = Substitute.For(), From 251c5547d476a8df0caeef4ed3a24ca9e63e9df5 Mon Sep 17 00:00:00 2001 From: Richard Gregory Date: Thu, 13 Feb 2025 22:35:18 +0100 Subject: [PATCH 15/15] fix: Resolve null reference and constructor argument errors in AdminRpcModule --- src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs | 1 + .../Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs | 3 ++- .../Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs | 4 +++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs b/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs index e646e5a9c94..b61c4d735b2 100644 --- a/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs +++ b/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs @@ -76,6 +76,7 @@ public virtual async Task Execute(CancellationToken cancellationToken) StepDependencyException.ThrowIfNull(_api.SyncServer); StepDependencyException.ThrowIfNull(_api.EngineSignerStore); StepDependencyException.ThrowIfNull(_api.EthereumEcdsa); + StepDependencyException.ThrowIfNull(_api.TrustedNodesManager); if (!_jsonRpcConfig.Enabled) { diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs index 474ee2154f7..c28bb3353e1 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs @@ -325,7 +325,8 @@ public async Task Test_admin_addTrustedPeer() _exampleDataDir, new ManualPruningTrigger(), chainSpec.Parameters, - trustedNodesManager); + trustedNodesManager, + _subscriptionManager); // Call admin_addTrustedPeer via the RPC test helper string serialized = await RpcTest.TestSerializedRequest(adminRpcModule, "admin_addTrustedPeer", _enodeString); diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs index 8f988c768f4..02e7742ee86 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs @@ -51,7 +51,9 @@ public AdminRpcModule( IAdminEraService eraService, string dataDir, ManualPruningTrigger pruningTrigger, - ChainParameters parameters) + ChainParameters parameters, + ITrustedNodesManager trustedNodesManager, + ISubscriptionManager subscriptionManager) { _enode = enode ?? throw new ArgumentNullException(nameof(enode)); _dataDir = dataDir ?? throw new ArgumentNullException(nameof(dataDir));