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

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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 @@ -203,6 +203,7 @@ public ISealEngine SealEngine
public IWorldStateManager? WorldStateManager { 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;

}
}
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 @@ -360,6 +360,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 = "PeersDB";
IFullDb peersDb = initConfig.DiagnosticMode == DiagnosticMode.MemDb
Expand Down Expand Up @@ -432,9 +435,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
4 changes: 3 additions & 1 deletion src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ public virtual async Task Execute(CancellationToken cancellationToken)

StepDependencyException.ThrowIfNull(_api.PeerManager);
StepDependencyException.ThrowIfNull(_api.StaticNodesManager);
StepDependencyException.ThrowIfNull(_api.TrustedNodesManager);
StepDependencyException.ThrowIfNull(_api.Enode);

ManualPruningTrigger pruningTrigger = new();
Expand All @@ -138,7 +139,8 @@ public virtual async Task Execute(CancellationToken cancellationToken)
_api.Enode,
initConfig.BaseDbPath,
pruningTrigger,
getFromApi.ChainSpec.Parameters);
getFromApi.ChainSpec.Parameters,
_api.TrustedNodesManager);
rpcModuleProvider.RegisterSingle<IAdminRpcModule>(adminRpcModule);

StepDependencyException.ThrowIfNull(_api.TxPoolInfoProvider);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public void Setup()
peerPool.ActivePeers.Returns(dict);

IStaticNodesManager staticNodesManager = Substitute.For<IStaticNodesManager>();
ITrustedNodesManager trustedNodesManager = Substitute.For<ITrustedNodesManager>();
Enode enode = new(_enodeString);
ChainSpec chainSpec = new()
{
Expand All @@ -68,7 +69,8 @@ public void Setup()
enode,
_exampleDataDir,
new ManualPruningTrigger(),
chainSpec.Parameters);
chainSpec.Parameters,
trustedNodesManager);

_serializer = new EthereumJsonSerializer();
}
Expand Down Expand Up @@ -135,6 +137,49 @@ 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<IBlockingVerifyTrie>(),
Substitute.For<IStateReader>(),
new Enode(_enodeString),
_exampleDataDir,
new ManualPruningTrigger(),
chainSpec.Parameters,
trustedNodesManager);

// 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
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class AdminRpcModule : IAdminRpcModule
private readonly IBlockingVerifyTrie _blockingVerifyTrie;
private readonly IStateReader _stateReader;
private NodeInfo _nodeInfo = null!;
private readonly ITrustedNodesManager _trustedNodesManager;

public AdminRpcModule(
IBlockTree blockTree,
Expand All @@ -43,7 +44,8 @@ public AdminRpcModule(
IEnode enode,
string dataDir,
ManualPruningTrigger pruningTrigger,
ChainParameters parameters)
ChainParameters parameters,
ITrustedNodesManager trustedNodesManager)
{
_enode = enode ?? throw new ArgumentNullException(nameof(enode));
_dataDir = dataDir ?? throw new ArgumentNullException(nameof(dataDir));
Expand All @@ -55,6 +57,7 @@ public AdminRpcModule(
_stateReader = stateReader ?? throw new ArgumentNullException(nameof(stateReader));
_pruningTrigger = pruningTrigger;
_parameters = parameters ?? throw new ArgumentNullException(nameof(parameters));
_trustedNodesManager = trustedNodesManager ?? throw new ArgumentNullException(nameof(trustedNodesManager));

BuildNodeInfo();
}
Expand Down Expand Up @@ -124,6 +127,24 @@ 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);
bool added = await _trustedNodesManager.AddAsync(enodeObj);

if (added)
{
_peerPool.GetOrAdd(new NetworkNode(enodeObj.ToString()));

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 @@ -79,4 +79,14 @@ ResultWrapper<PeerInfo[]> admin_peers(
ExampleResponse = "\"Starting\"",
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
);
}
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);
}
}
17 changes: 16 additions & 1 deletion src/Nethermind/Nethermind.Network/PeerPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,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 @@ -50,14 +51,17 @@ public PeerPool(
INodeStatsManager nodeStatsManager,
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,6 +105,17 @@ 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);

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;
}

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

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