Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Feature/unified plugin Enabled flag #8077

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
19 changes: 8 additions & 11 deletions src/Nethermind/Nethermind.Analytics/AnalyticsPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@

namespace Nethermind.Analytics
{
public class AnalyticsPlugin : INethermindPlugin
public class AnalyticsPlugin(IInitConfig initConfig, IAnalyticsConfig analyticsConfig) : INethermindPlugin
{
private IAnalyticsConfig _analyticsConfig;
private IList<IPublisher> _publishers;
private INethermindApi _api;

private bool _isOn;
private bool _isOn = initConfig.WebSocketsEnabled &&
(analyticsConfig.PluginsEnabled ||
analyticsConfig.StreamBlocks ||
analyticsConfig.StreamTransactions);

public bool Enabled => _isOn;

public ValueTask DisposeAsync() { return ValueTask.CompletedTask; }

Expand All @@ -32,13 +36,6 @@ public Task Init(INethermindApi api)
{
_api = api;
var (getFromAPi, _) = _api.ForInit;
_analyticsConfig = getFromAPi.Config<IAnalyticsConfig>();

IInitConfig initConfig = getFromAPi.Config<IInitConfig>();
_isOn = initConfig.WebSocketsEnabled &&
(_analyticsConfig.PluginsEnabled ||
_analyticsConfig.StreamBlocks ||
_analyticsConfig.StreamTransactions);

if (!_isOn)
{
Expand All @@ -57,7 +54,7 @@ public Task Init(INethermindApi api)

private void TxPoolOnNewDiscovered(object sender, TxEventArgs e)
{
if (_analyticsConfig.StreamTransactions)
if (analyticsConfig.StreamTransactions)
{
foreach (IPublisher publisher in _publishers)
{
Expand Down
50 changes: 50 additions & 0 deletions src/Nethermind/Nethermind.Api.Test/PluginLoaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@
using System;
using System.Collections.Generic;
using System.IO.Abstractions;
using System.Threading.Tasks;
using FluentAssertions;
using Nethermind.Api.Extensions;
using Nethermind.Config;
using Nethermind.Consensus.AuRa;
using Nethermind.Consensus.Clique;
using Nethermind.Consensus.Ethash;
using Nethermind.HealthChecks;
using Nethermind.Hive;
using Nethermind.Logging;
using Nethermind.Merge.Plugin;
using Nethermind.Specs.ChainSpecStyle;
using NSubstitute;
using NUnit.Framework;

Expand Down Expand Up @@ -106,4 +110,50 @@ public void default_config()
};
Assert.That(expected, Is.EqualTo(loader.PluginTypes).AsCollection);
}

[Test]
public async Task Can_PassInConfig_And_OnlyLoadEnabledPlugins()
{
PluginLoader loader = new PluginLoader(string.Empty, Substitute.For<IFileSystem>(), new TestLogManager().GetClassLogger(),
typeof(TestPlugin1), typeof(TestPlugin2));
loader.Load();

IConfigProvider configProvider = new ConfigProvider();
IInitConfig initConfig = configProvider.GetConfig<IInitConfig>();
initConfig.DiscoveryEnabled = true;
initConfig.PeerManagerEnabled = false;
ChainSpec chainSpec = new ChainSpec();
chainSpec.ChainId = 999;

IList<INethermindPlugin> loadedPlugins = await loader.LoadPlugins(configProvider, chainSpec);
loadedPlugins.Should().BeEquivalentTo([new TestPlugin1(chainSpec, initConfig)]);
}

private class TestPlugin1(ChainSpec chainSpec, IInitConfig initConfig) : INethermindPlugin
{
public string Name => "TestPlugin1";
public string Description => "TestPlugin1";
public string Author => "TestPlugin1";

// Just some arbitrary combination
public bool Enabled => chainSpec.ChainId == 999 && initConfig.DiscoveryEnabled && !initConfig.PeerManagerEnabled;

public ValueTask DisposeAsync()
{
return ValueTask.CompletedTask;
}
}

private class TestPlugin2() : INethermindPlugin
{
public string Name => "TestPlugin2";
public string Description => "TestPlugin2";
public string Author => "TestPlugin2";
public bool Enabled => false;

public ValueTask DisposeAsync()
{
return ValueTask.CompletedTask;
}
}
}
2 changes: 2 additions & 0 deletions src/Nethermind/Nethermind.Api.Test/TestPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,7 @@ public Task InitRpcModules()
{
throw new System.NotImplementedException();
}

public bool Enabled => true;
}
}
2 changes: 2 additions & 0 deletions src/Nethermind/Nethermind.Api.Test/TestPlugin2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,7 @@ public Task InitRpcModules()
{
throw new System.NotImplementedException();
}

public bool Enabled => true;
}
}
2 changes: 0 additions & 2 deletions src/Nethermind/Nethermind.Api/Extensions/IConsensusPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ namespace Nethermind.Api.Extensions
{
public interface IConsensusPlugin : INethermindPlugin, IBlockProducerFactory
{
string SealEngineType { get; }

INethermindApi CreateApi(IConfigProvider configProvider, IJsonSerializer jsonSerializer,
ILogManager logManager, ChainSpec chainSpec) => new NethermindApi(configProvider, jsonSerializer, logManager, chainSpec);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,5 @@ public interface IConsensusWrapperPlugin : INethermindPlugin
/// Priorities for ordering multiple plugin. Only used to determine the wrapping order of block production.
/// </summary>
int Priority => 0;

bool Enabled { get; }
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System.Collections.Generic;
using Nethermind.Api.Steps;

namespace Nethermind.Api.Extensions;

/// <summary>
Expand All @@ -9,11 +12,5 @@ namespace Nethermind.Api.Extensions;
/// </summary>
public interface IInitializationPlugin : INethermindPlugin
{
/// <summary>
/// This method will be called on the plugin instance
/// decide whether or not we need to run initialization steps
/// defined in its assembly. It receives the api to be able to
/// look at the config.
/// </summary>
bool ShouldRunSteps(INethermindApi api);
IEnumerable<StepInfo> GetSteps();
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ void InitTxTypesAndRlpDecoders(INethermindApi api) { }
Task InitRpcModules() => Task.CompletedTask;

bool MustInitialize => false;
bool Enabled { get; }
asdacap marked this conversation as resolved.
Show resolved Hide resolved
}
75 changes: 75 additions & 0 deletions src/Nethermind/Nethermind.Api/Extensions/PluginLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Threading.Tasks;
using Nethermind.Config;
using Nethermind.Logging;
using Nethermind.Specs.ChainSpecStyle;

namespace Nethermind.Api.Extensions;

Expand Down Expand Up @@ -125,4 +129,75 @@ public void OrderPlugins(IPluginConfig pluginConfig)
return fPos.CompareTo(sPos);
});
}

private bool TryResolveParameter(IConfigProvider configProvider, ChainSpec chainSpec, Type parameterType, [NotNullWhen(true)] out object? parameter)
{
if (parameterType == typeof(ChainSpec))
{
parameter = chainSpec;
return true;
}

if (parameterType.IsAssignableTo(typeof(IConfig)))
{
parameter = configProvider.GetConfig(parameterType);
return true;
}

parameter = null;
return false;
}

private INethermindPlugin CreatePluginInstance(IConfigProvider configProvider, ChainSpec chainSpec, Type plugin)
{
ConstructorInfo[] constructors = plugin.GetConstructors();
// For simplicity and to prevent mistake, only one constructor should be defined.
if (constructors.Length != 1) throw new Exception($"Plugin {plugin.Name} has more than one constructor");

ConstructorInfo constructor = constructors[0];
object[] parameters = new object[constructor.GetParameters().Length];
for (int i = 0; i < constructor.GetParameters().Length; i++)
{
ParameterInfo parameter = constructor.GetParameters()[i];

if (!TryResolveParameter(configProvider, chainSpec, parameter.ParameterType, out parameters[i]!))
{
throw new Exception($"Failed to resolve parameter {parameter.ParameterType} for plugin {plugin.Name} construction");
}
}

return (INethermindPlugin)constructor.Invoke(parameters);
}

public async Task<IList<INethermindPlugin>> LoadPlugins(IConfigProvider configProvider, ChainSpec chainSpec)
{
List<INethermindPlugin> plugins = new List<INethermindPlugin>();
if (_logger.IsInfo) _logger.Info($"Detected {PluginTypes.Count()} plugins");
foreach (Type pluginType in PluginTypes)
{
try
{
INethermindPlugin plugin = CreatePluginInstance(configProvider, chainSpec, pluginType);
if (_logger.IsInfo)
{
string pluginName = $"{plugin.Name} by {plugin.Author}";
_logger.Info($" {pluginName,-30} {(plugin.Enabled ? "Enabled" : "Disabled")}");
}
if (plugin.Enabled)
{
plugins.Add(plugin);
}
else
{
await plugin.DisposeAsync();
}
}
catch (Exception ex)
{
if (_logger.IsError) _logger.Error($"Failed to create plugin {pluginType.FullName}", ex);
}
}

return plugins;
}
}
2 changes: 1 addition & 1 deletion src/Nethermind/Nethermind.Api/IBasicApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public interface IBasicApi
public IConsensusPlugin? GetConsensusPlugin() =>
Plugins
.OfType<IConsensusPlugin>()
.SingleOrDefault(cp => cp.SealEngineType == SealEngineType);
.SingleOrDefault();

public IEnumerable<IConsensusWrapperPlugin> GetConsensusWrapperPlugins() =>
Plugins.OfType<IConsensusWrapperPlugin>().Where(static p => p.Enabled);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System.Threading;
using System.Threading.Tasks;

namespace Nethermind.Init.Steps
namespace Nethermind.Api.Steps
{
public interface IStep
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

using System;

namespace Nethermind.Init.Steps
namespace Nethermind.Api.Steps
{
[AttributeUsage(AttributeTargets.Class)]
public class RunnerStepDependenciesAttribute : Attribute
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,24 @@
using System;
using System.Reflection;

namespace Nethermind.Init.Steps
namespace Nethermind.Api.Steps
{
public class StepInfo
{
public StepInfo(Type type, Type baseType)
public StepInfo(Type type)
{
if (type.IsAbstract)
{
throw new ArgumentException("Step type cannot be abstract", nameof(type));
}

if (!IsStepType(type))
{
throw new ArgumentException($"{type.FullName} is not a step type");
}

StepType = type;
StepBaseType = baseType;
StepBaseType = GetStepBaseType(type);

RunnerStepDependenciesAttribute? dependenciesAttribute =
StepType.GetCustomAttribute<RunnerStepDependenciesAttribute>();
Expand All @@ -29,11 +34,26 @@ public StepInfo(Type type, Type baseType)

public Type[] Dependencies { get; }

public StepInitializationStage Stage { get; set; }

public override string ToString()
{
return $"{StepType.Name} : {StepBaseType.Name} ({Stage})";
return $"{StepType.Name} : {StepBaseType.Name}";
}

private static Type GetStepBaseType(Type type)
{
while (type.BaseType is not null && IsStepType(type.BaseType))
{
type = type.BaseType;
}

return type;
}

public static implicit operator StepInfo(Type type)
{
return new StepInfo(type);
}

public static bool IsStepType(Type t) => typeof(IStep).IsAssignableFrom(t);
}
}
2 changes: 1 addition & 1 deletion src/Nethermind/Nethermind.AuRa.Test/AuRaPluginTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ public class AuRaPluginTests
[Test]
public void Init_when_not_AuRa_doesnt_trow()
{
AuRaPlugin auRaPlugin = new();
ChainSpec chainSpec = new();
AuRaPlugin auRaPlugin = new(chainSpec);
chainSpec.EngineChainSpecParametersProvider = new TestChainSpecParametersProvider(new AuRaChainSpecEngineParameters());
Action init = () => auRaPlugin.Init(new AuRaNethermindApi(new ConfigProvider(), new EthereumJsonSerializer(), new TestLogManager(), chainSpec));
init.Should().NotThrow();
Expand Down
6 changes: 3 additions & 3 deletions src/Nethermind/Nethermind.Config/ConfigProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Nethermind.Config;

public class ConfigProvider : IConfigProvider
{
private readonly ConcurrentDictionary<Type, object> _instances = new();
private readonly ConcurrentDictionary<Type, IConfig> _instances = new();

private readonly List<IConfigSource> _configSource = [];
private Dictionary<string, object> Categories { get; set; } = new(StringComparer.InvariantCultureIgnoreCase);
Expand All @@ -25,7 +25,7 @@ public T GetConfig<T>() where T : IConfig
return (T)GetConfig(typeof(T));
}

public object GetConfig(Type configType)
public IConfig GetConfig(Type configType)
{
if (!typeof(IConfig).IsAssignableFrom(configType)) throw new ArgumentException($"Type {configType} is not {typeof(IConfig)}");

Expand Down Expand Up @@ -79,7 +79,7 @@ public void Initialize()
_implementations[@interface] = directImplementation;

object config = Activator.CreateInstance(_implementations[@interface]);
_instances[@interface] = config!;
_instances[@interface] = (IConfig)config!;

foreach (PropertyInfo propertyInfo in config.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
Expand Down
Loading
Loading