Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Implement admin_addTrustedPeer rpc endpoint #7891

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Nethermind/Nethermind.Api/IApiWithNetwork.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
3 changes: 3 additions & 0 deletions src/Nethermind/Nethermind.Api/IInitConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down
1 change: 1 addition & 0 deletions src/Nethermind/Nethermind.Api/InitConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/Nethermind/Nethermind.Api/NethermindApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
4 changes: 4 additions & 0 deletions src/Nethermind/Nethermind.Cli/Modules/AdminCliModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>("admin_removePeer", enode, removeFromStaticNodes).Result;

[CliFunction("admin", "addTrustedPeer", Description = "Adds given node to the trusted nodes")]
public bool AddTrustedPeer(string enode) => NodeManager.Post<bool>("admin_addTrustedPeer", enode).Result;

}
}
7 changes: 7 additions & 0 deletions src/Nethermind/Nethermind.Config/NetworkNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ public NetworkNode(PublicKey publicKey, string ip, int port, long reputation = 0
Reputation = reputation;
}

public NetworkNode(Enode enode)
{
_enode = enode;
}

public Enode Enode => _enode;

public PublicKey NodeId => _enode.PublicKey;
public string Host => _enode.HostIp.ToString();
public int Port => _enode.Port;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ protected override void Load(ContainerBuilder builder)
// This load from file.
.AddSingleton<NodesLoader>()

.AddSingleton<ITrustedNodesManager, ILogManager>((logManager) =>
new TrustedNodesManager(initConfig.TrustedNodesPath, logManager))

.Bind<INodeSource, IStaticNodesManager>()
.Bind<INodeSource, NodesLoader>()
.AddComposite<INodeSource, CompositeNodeSource>()
Expand Down
9 changes: 6 additions & 3 deletions src/Nethermind/Nethermind.Init/Steps/InitializeNetwork.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -120,6 +121,10 @@ public virtual async Task Execute(CancellationToken cancellationToken)
_api.KeyStore);
rpcModuleProvider.RegisterSingle<IPersonalRpcModule>(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;
Expand All @@ -141,6 +146,7 @@ public virtual async Task Execute(CancellationToken cancellationToken)
initConfig.BaseDbPath,
pruningTrigger,
getFromApi.ChainSpec.Parameters,
_api.TrustedNodesManager,
subscriptionManager);
rpcModuleProvider.RegisterSingle<IAdminRpcModule>(adminRpcModule);

Expand Down
47 changes: 47 additions & 0 deletions src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public void Setup()

IJsonSerializer jsonSerializer = new EthereumJsonSerializer();
IStaticNodesManager staticNodesManager = Substitute.For<IStaticNodesManager>();
ITrustedNodesManager trustedNodesManager = Substitute.For<ITrustedNodesManager>();
Enode enode = new(_enodeString);
ChainSpec chainSpec = new()
{
Expand All @@ -116,6 +117,7 @@ public void Setup()
_exampleDataDir,
new ManualPruningTrigger(),
chainSpec.Parameters,
trustedNodesManager,
_subscriptionManager);
_adminRpcModule.Context = new JsonRpcContext(RpcEndpoint.Ws, _jsonRpcDuplexClient);

Expand Down Expand Up @@ -297,6 +299,51 @@ public async Task Test_hasStateForBlock()
(await RpcTest.TestSerializedRequest(_adminRpcModule, "admin_isStateRootAvailable", "latest")).Should().Contain("true");
}

[Test]
public async Task Test_admin_addTrustedPeer()
{
// Setup dependencies
ITrustedNodesManager trustedNodesManager = Substitute.For<ITrustedNodesManager>();
IPeerPool peerPool = Substitute.For<IPeerPool>();

ChainSpec chainSpec = new() { Parameters = new ChainParameters() };
Enode testEnode = new(_enodeString);

// Mock AddAsync to return true for any enode (to simplify)
trustedNodesManager.AddAsync(Arg.Any<Enode>(), Arg.Any<bool>()).Returns(Task.FromResult(true));

// Create the adminRpcModule as IAdminRpcModule (important for RpcTest)
IAdminRpcModule adminRpcModule = new AdminRpcModule(
_blockTree,
_networkConfig,
peerPool,
Substitute.For<IStaticNodesManager>(),
Substitute.For<IVerifyTrieStarter>(),
Substitute.For<IStateReader>(),
new Enode(_enodeString),
Substitute.For<IAdminEraService>(),
_exampleDataDir,
new ManualPruningTrigger(),
chainSpec.Parameters,
trustedNodesManager,
_subscriptionManager);

// Call admin_addTrustedPeer via the RPC test helper
string serialized = await RpcTest.TestSerializedRequest(adminRpcModule, "admin_addTrustedPeer", _enodeString);

// Deserialize the response
JsonRpcSuccessResponse response = _serializer.Deserialize<JsonRpcSuccessResponse>(serialized);
response.Should().NotBeNull("Response should not be null");
bool result = ((JsonElement)response.Result!).Deserialize<bool>(EthereumJsonSerializer.JsonOptions);
result.Should().BeTrue("The RPC call should succeed and return true");

// Verify that AddAsync was called once with any Enode
await trustedNodesManager.Received(1).AddAsync(Arg.Any<Enode>(), Arg.Any<bool>());

// Verify that peerPool.GetOrAdd was called once with any NetworkNode
peerPool.Received(1).GetOrAdd(Arg.Any<NetworkNode>());
}

[Test]
public async Task Smoke_solc()
{
Expand Down
19 changes: 19 additions & 0 deletions src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -51,6 +52,7 @@ public AdminRpcModule(
string dataDir,
ManualPruningTrigger pruningTrigger,
ChainParameters parameters,
ITrustedNodesManager trustedNodesManager,
ISubscriptionManager subscriptionManager)
{
_enode = enode ?? throw new ArgumentNullException(nameof(enode));
Expand All @@ -64,6 +66,7 @@ public AdminRpcModule(
_pruningTrigger = pruningTrigger;
_eraService = eraService;
_parameters = parameters ?? throw new ArgumentNullException(nameof(parameters));
_trustedNodesManager = trustedNodesManager ?? throw new ArgumentNullException(nameof(trustedNodesManager));

BuildNodeInfo();
_subscriptionManager = subscriptionManager;
Expand Down Expand Up @@ -134,6 +137,22 @@ public async Task<ResultWrapper<string>> admin_removePeer(string enode, bool rem
: ResultWrapper<string>.Fail("Failed to remove peer.");
}

public async Task<ResultWrapper<bool>> admin_addTrustedPeer(string enode)
{
Enode enodeObj = new(enode);

if (_trustedNodesManager.IsTrusted(enodeObj) || await _trustedNodesManager.AddAsync(enodeObj, updateFile: true))
{
_peerPool.GetOrAdd(new NetworkNode(enodeObj));
return ResultWrapper<bool>.Success(true);
}
else
{
return ResultWrapper<bool>.Fail("Failed to add trusted peer.");
}
}


public ResultWrapper<PeerInfo[]> admin_peers(bool includeDetails = false)
=> ResultWrapper<PeerInfo[]>.Success(
_peerPool.ActivePeers.Select(p => new PeerInfo(p.Value, includeDetails)).ToArray());
Expand Down
10 changes: 10 additions & 0 deletions src/Nethermind/Nethermind.JsonRpc/Modules/Admin/IAdminRpcModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ Task<ResultWrapper<string>> admin_importHistory(
IsImplemented = true)]
ResultWrapper<string> 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<ResultWrapper<bool>> 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<string> admin_subscribe(string subscriptionName, string? args = null);
[JsonRpcMethod(Description = "Unsubscribes from a subscription.", IsImplemented = true, IsSharable = false, Availability = RpcEndpoint.All & ~RpcEndpoint.Http)]
Expand Down
3 changes: 3 additions & 0 deletions src/Nethermind/Nethermind.Network.Stats/Model/Node.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ public sealed class Node : IFormattable, IEquatable<Node>
/// </summary>
public bool IsStatic { get; set; }

public bool IsTrusted { get; set; }


public string ClientId
{
get => _clientId;
Expand Down
3 changes: 2 additions & 1 deletion src/Nethermind/Nethermind.Network.Test/PeerManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,8 @@ public Context(int parallelism = 0, int maxActivePeers = 25)
StaticNodesManager.DiscoverNodes(Arg.Any<CancellationToken>()).Returns(AsyncEnumerable.Empty<Node>());
TestNodeSource = new TestNodeSource();
CompositeNodeSource nodeSources = new(NodesLoader, DiscoveryApp, StaticNodesManager, TestNodeSource);
PeerPool = new PeerPool(nodeSources, Stats, Storage, NetworkConfig, LimboLogs.Instance);
ITrustedNodesManager trustedNodesManager = Substitute.For<ITrustedNodesManager>();
PeerPool = new PeerPool(nodeSources, Stats, Storage, NetworkConfig, LimboLogs.Instance, trustedNodesManager);
CreatePeerManager();
}

Expand Down
5 changes: 4 additions & 1 deletion src/Nethermind/Nethermind.Network.Test/PeerPoolTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public class PeerPoolTests
[Test]
public async Task PeerPool_ShouldThrottleSource_WhenFull()
{
var trustedNodesManager = Substitute.For<ITrustedNodesManager>();

TestNodeSource nodeSource = new TestNodeSource();
PeerPool pool = new PeerPool(
nodeSource,
Expand All @@ -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) =>
Expand Down
18 changes: 18 additions & 0 deletions src/Nethermind/Nethermind.Network/ITrustedNodesManager.cs
Original file line number Diff line number Diff line change
@@ -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<NetworkNode> Nodes { get; }
Task InitAsync();
Task<bool> AddAsync(Enode enode, bool updateFile = true);
Task<bool> RemoveAsync(Enode enode, bool updateFile = true);
bool IsTrusted(Enode enode);
}
}
9 changes: 7 additions & 2 deletions src/Nethermind/Nethermind.Network/PeerPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PublicKeyAsKey, Peer> ActivePeers { get; } = new();
public ConcurrentDictionary<PublicKeyAsKey, Peer> Peers { get; } = new();
Expand All @@ -51,14 +52,17 @@ 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));
_peerStorage = peerStorage ?? throw new ArgumentNullException(nameof(peerStorage));
_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;
Expand Down Expand Up @@ -101,7 +105,8 @@ private Peer CreateNew(PublicKeyAsKey key, (Node Node, ConcurrentDictionary<Publ

private Peer CreateNew(PublicKeyAsKey key, (NetworkNode Node, ConcurrentDictionary<PublicKeyAsKey, Peer> Statics) arg)
{
Node node = new(arg.Node);
Node node = new(arg.Node) { IsTrusted = _trustedNodesManager.IsTrusted(arg.Node.Enode) };

Peer peer = new(node, _stats.GetOrAdd(node));

PeerAdded?.Invoke(this, new PeerEventArgs(peer));
Expand Down
Loading