diff --git a/sdk/node/Libplanet.Node.Executable/Program.cs b/sdk/node/Libplanet.Node.Executable/Program.cs index 03dc0519f13..2d95e1c6fb1 100644 --- a/sdk/node/Libplanet.Node.Executable/Program.cs +++ b/sdk/node/Libplanet.Node.Executable/Program.cs @@ -1,3 +1,4 @@ +using Libplanet.Node.API.Services; using Libplanet.Node.Extensions; using Libplanet.Node.Options.Schema; using Microsoft.AspNetCore.Server.Kestrel.Core; @@ -24,9 +25,7 @@ // Add services to the container. builder.Services.AddGrpc(); builder.Services.AddGrpcReflection(); -var libplanetBuilder = builder.Services.AddLibplanetNode(builder.Configuration) - .WithSwarm() - .WithValidator(); +var libplanetBuilder = builder.Services.AddLibplanetNode(builder.Configuration); var app = builder.Build(); var handlerMessage = """ @@ -36,7 +35,8 @@ Communication with gRPC endpoints must be made through a gRPC client. To learn h var schema = await OptionsSchemaBuilder.GetSchemaAsync(default); // Configure the HTTP request pipeline. -app.MapGrpcServiceFromDomain(libplanetBuilder.Scopes); +app.MapGrpcService(); +app.MapGrpcService(); app.MapGet("/", () => handlerMessage); app.MapGet("/schema", () => schema); diff --git a/sdk/node/Libplanet.Node.Executable/Services/BlockChainGrpcService.cs b/sdk/node/Libplanet.Node.Executable/Services/BlockChainGrpcService.cs index bc9c900f9d5..0b7fbbf935d 100644 --- a/sdk/node/Libplanet.Node.Executable/Services/BlockChainGrpcService.cs +++ b/sdk/node/Libplanet.Node.Executable/Services/BlockChainGrpcService.cs @@ -1,12 +1,10 @@ using Grpc.Core; using Libplanet.Common; -using Libplanet.Node.DependencyInjection; using Libplanet.Node.Services; using Libplanet.Types.Blocks; namespace Libplanet.Node.API.Services; -[Grpc] public class BlockChainGrpcService( IReadChainService blockChain) : BlockChain.BlockChainBase diff --git a/sdk/node/Libplanet.Node.Executable/Services/SchemaGrpcService.cs b/sdk/node/Libplanet.Node.Executable/Services/SchemaGrpcService.cs index 07ff012da67..aad9b9d80a9 100644 --- a/sdk/node/Libplanet.Node.Executable/Services/SchemaGrpcService.cs +++ b/sdk/node/Libplanet.Node.Executable/Services/SchemaGrpcService.cs @@ -1,10 +1,9 @@ using Grpc.Core; -using Libplanet.Node.DependencyInjection; +using Libplanet.Node.Options; using Libplanet.Node.Options.Schema; namespace Libplanet.Node.API.Services; -[Grpc] public class SchemaGrpcService : Schema.SchemaBase { private string[]? _list; diff --git a/sdk/node/Libplanet.Node.Executable/Services/SeedGrpcService.cs b/sdk/node/Libplanet.Node.Executable/Services/SeedGrpcService.cs index b1b7f8fa004..bdc13f3f6ea 100644 --- a/sdk/node/Libplanet.Node.Executable/Services/SeedGrpcService.cs +++ b/sdk/node/Libplanet.Node.Executable/Services/SeedGrpcService.cs @@ -1,10 +1,8 @@ using Grpc.Core; -using Libplanet.Node.DependencyInjection; using Libplanet.Node.Services; namespace Libplanet.Node.API.Services; -[Grpc(Scope = "Seed")] public class SeedGrpcService( IServiceProvider serviceProvider) : Seed.SeedBase diff --git a/sdk/node/Libplanet.Node.Executable/appsettings-schema.json b/sdk/node/Libplanet.Node.Executable/appsettings-schema.json index 8d5b1a37f80..e62fc67e480 100644 --- a/sdk/node/Libplanet.Node.Executable/appsettings-schema.json +++ b/sdk/node/Libplanet.Node.Executable/appsettings-schema.json @@ -1109,79 +1109,22 @@ } } }, - "BlocksyncSeed": { - "title": "SeedOptions", + "Solo": { + "title": "SoloOptions", "type": "object", "additionalProperties": false, "properties": { - "PrivateKey": { - "type": "string", - "description": "The private key of the seed node.", - "pattern": "^[0-9a-fA-F]{64}$" - }, - "EndPoint": { - "type": "string", - "description": "The endpoint of the seed node.", - "pattern": "^(?:(?:[a-zA-Z0-9\\-\\.]+)|(?:\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})):\\d{1,5}$" + "IsEnabled": { + "type": "boolean" }, - "RefreshInterval": { + "BlockInterval": { "type": "integer", - "description": "The interval to refresh the peer list.", - "format": "int32", - "maximum": 2147483647.0, - "minimum": 0.0 + "format": "int64" }, - "PeerLifetime": { - "type": "integer", - "description": "The lifetime of a peer.", - "format": "int32", - "maximum": 2147483647.0, - "minimum": 0.0 - }, - "PingTimeout": { - "type": "integer", - "description": "The timeout for a ping.", - "format": "int32", - "maximum": 2147483647.0, - "minimum": 0.0 - } - } - }, - "ConsensusSeed": { - "title": "SeedOptions", - "type": "object", - "additionalProperties": false, - "properties": { "PrivateKey": { "type": "string", - "description": "The private key of the seed node.", + "description": "The private key of the node.", "pattern": "^[0-9a-fA-F]{64}$" - }, - "EndPoint": { - "type": "string", - "description": "The endpoint of the seed node.", - "pattern": "^(?:(?:[a-zA-Z0-9\\-\\.]+)|(?:\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})):\\d{1,5}$" - }, - "RefreshInterval": { - "type": "integer", - "description": "The interval to refresh the peer list.", - "format": "int32", - "maximum": 2147483647.0, - "minimum": 0.0 - }, - "PeerLifetime": { - "type": "integer", - "description": "The lifetime of a peer.", - "format": "int32", - "maximum": 2147483647.0, - "minimum": 0.0 - }, - "PingTimeout": { - "type": "integer", - "description": "The timeout for a ping.", - "format": "int32", - "maximum": 2147483647.0, - "minimum": 0.0 } } }, @@ -1231,6 +1174,9 @@ "type": "object", "additionalProperties": false, "properties": { + "IsEnabled": { + "type": "boolean" + }, "PrivateKey": { "type": "string", "description": "The private key of the node.", @@ -1252,6 +1198,10 @@ "type": "object", "additionalProperties": false, "properties": { + "IsEnabled": { + "type": "boolean", + "default": true + }, "EndPoint": { "type": "string", "pattern": "^(?:(?:[a-zA-Z0-9\\-\\.]+)|(?:\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})):\\d{1,5}$" @@ -1275,13 +1225,9 @@ "description": "Options for the genesis block.", "$ref": "#/definitions/Genesis" }, - "BlocksyncSeed": { - "description": "Type 'SeedOptions' does not have a description.", - "$ref": "#/definitions/BlocksyncSeed" - }, - "ConsensusSeed": { - "description": "Type 'SeedOptions' does not have a description.", - "$ref": "#/definitions/ConsensusSeed" + "Solo": { + "description": "Type 'SoloOptions' does not have a description.", + "$ref": "#/definitions/Solo" }, "Store": { "description": "Type 'StoreOptions' does not have a description.", diff --git a/sdk/node/Libplanet.Node.Executable/appsettings.Development.json b/sdk/node/Libplanet.Node.Executable/appsettings.Development.json index b39b643db3b..8b59c49d98e 100644 --- a/sdk/node/Libplanet.Node.Executable/appsettings.Development.json +++ b/sdk/node/Libplanet.Node.Executable/appsettings.Development.json @@ -10,5 +10,11 @@ "EndpointDefaults": { "Protocols": "Http1AndHttp2" } + }, + "Swarm": { + "IsEnabled": true + }, + "Validator": { + "IsEnabled": true } } diff --git a/sdk/node/Libplanet.Node.Extensions/LibplanetServicesExtensions.cs b/sdk/node/Libplanet.Node.Extensions/LibplanetServicesExtensions.cs index c6934494780..e6097fd520e 100644 --- a/sdk/node/Libplanet.Node.Extensions/LibplanetServicesExtensions.cs +++ b/sdk/node/Libplanet.Node.Extensions/LibplanetServicesExtensions.cs @@ -1,7 +1,10 @@ +using Libplanet.Blockchain; using Libplanet.Node.Extensions.NodeBuilder; using Libplanet.Node.Options; +using Libplanet.Node.Services; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; namespace Libplanet.Node.Extensions; @@ -13,9 +16,54 @@ public static ILibplanetNodeBuilder AddLibplanetNode( { SynchronizationContext.SetSynchronizationContext(SynchronizationContext.Current ?? new()); services.AddSingleton(SynchronizationContext.Current!); - services.Configure(configuration.GetSection(SoloProposeOption.Position)); - services.AddOptionsFromDomain(configuration); + services.AddOptions() + .Bind(configuration.GetSection(GenesisOptions.Position)); + services.AddSingleton, GenesisOptionsConfigurator>(); - return new LibplanetNodeBuilder(services, configuration); + services.AddOptions() + .Bind(configuration.GetSection(StoreOptions.Position)); + services.AddSingleton, StoreOptionsConfigurator>(); + + services.AddOptions() + .Bind(configuration.GetSection(SwarmOptions.Position)); + services.AddSingleton, SwarmOptionsConfigurator>(); + services.AddSingleton, SwarmOptionsValidator>(); + + services.AddOptions() + .Bind(configuration.GetSection(ValidatorOptions.Position)); + services.AddSingleton, ValidatorOptionsConfigurator>(); + services.AddSingleton, ValidatorOptionsValidator>(); + + services.AddOptions() + .Bind(configuration.GetSection(SoloOptions.Position)); + services.AddSingleton, SoloOptionsConfigurator>(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + var serviceProvider = services.BuildServiceProvider(); + var soloOptions = serviceProvider.GetRequiredService>(); + var swarmOptions = serviceProvider.GetRequiredService>(); + var validatorOptions = serviceProvider.GetRequiredService>(); + var nodeBuilder = new LibplanetNodeBuilder(services); + + if (soloOptions.Value.IsEnabled) + { + nodeBuilder.WithSolo(); + } + + if (swarmOptions.Value.IsEnabled) + { + nodeBuilder.WithSwarm(); + } + + if (validatorOptions.Value.IsEnabled) + { + nodeBuilder.WithValidator(); + } + + return nodeBuilder; } } diff --git a/sdk/node/Libplanet.Node.Extensions/NodeBuilder/ILibplanetNodeBuilder.cs b/sdk/node/Libplanet.Node.Extensions/NodeBuilder/ILibplanetNodeBuilder.cs index 07817ef533b..b613f1e65a7 100644 --- a/sdk/node/Libplanet.Node.Extensions/NodeBuilder/ILibplanetNodeBuilder.cs +++ b/sdk/node/Libplanet.Node.Extensions/NodeBuilder/ILibplanetNodeBuilder.cs @@ -6,13 +6,9 @@ public interface ILibplanetNodeBuilder { IServiceCollection Services { get; } - string[] Scopes { get; } - ILibplanetNodeBuilder WithSolo(); ILibplanetNodeBuilder WithSwarm(); ILibplanetNodeBuilder WithValidator(); - - ILibplanetNodeBuilder WithSeed(); } diff --git a/sdk/node/Libplanet.Node.Extensions/NodeBuilder/LibplanetNodeBuilder.cs b/sdk/node/Libplanet.Node.Extensions/NodeBuilder/LibplanetNodeBuilder.cs index c048ad1ae11..0a0613774c2 100644 --- a/sdk/node/Libplanet.Node.Extensions/NodeBuilder/LibplanetNodeBuilder.cs +++ b/sdk/node/Libplanet.Node.Extensions/NodeBuilder/LibplanetNodeBuilder.cs @@ -1,19 +1,15 @@ using Libplanet.Node.Services; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace Libplanet.Node.Extensions.NodeBuilder; public class LibplanetNodeBuilder : ILibplanetNodeBuilder { - private readonly IConfiguration _configuration; private readonly List _scopeList = [string.Empty]; - internal LibplanetNodeBuilder(IServiceCollection services, IConfiguration configuration) + internal LibplanetNodeBuilder(IServiceCollection services) { Services = services; - _configuration = configuration; - Services.AddSingletonsFromDomain(); } public IServiceCollection Services { get; } @@ -23,30 +19,22 @@ internal LibplanetNodeBuilder(IServiceCollection services, IConfiguration config public ILibplanetNodeBuilder WithSolo() { Services.AddHostedService(); + _scopeList.Add("Solo"); return this; } public ILibplanetNodeBuilder WithSwarm() { - Services.AddSingletonsFromDomain(scope: "Swarm"); - Services.AddOptionsFromDomain(_configuration, scope: "Swarm"); + Services.AddSingleton(); + Services.AddHostedService(); _scopeList.Add("Swarm"); return this; } public ILibplanetNodeBuilder WithValidator() { - Services.AddSingletonsFromDomain(scope: "Validator"); - Services.AddOptionsFromDomain(_configuration, scope: "Validator"); + Services.AddSingleton(); _scopeList.Add("Validator"); return this; } - - public ILibplanetNodeBuilder WithSeed() - { - Services.AddSingletonsFromDomain(scope: "Seed"); - Services.AddOptionsFromDomain(_configuration, scope: "Seed"); - _scopeList.Add("Seed"); - return this; - } } diff --git a/sdk/node/Libplanet.Node.Tests/Services/BlockChainServiceTest.cs b/sdk/node/Libplanet.Node.Tests/Services/BlockChainServiceTest.cs index de940422d24..dd8461585ed 100644 --- a/sdk/node/Libplanet.Node.Tests/Services/BlockChainServiceTest.cs +++ b/sdk/node/Libplanet.Node.Tests/Services/BlockChainServiceTest.cs @@ -1,6 +1,5 @@ using Libplanet.Blockchain; using Libplanet.Crypto; -using Libplanet.Node.Extensions; using Libplanet.Node.Options; using Libplanet.Node.Services; using Microsoft.Extensions.DependencyInjection; @@ -12,37 +11,18 @@ namespace Libplanet.Node.Tests.Services; public class BlockChainServiceTest { - [Fact] - public void CreateWithInvalidOptions_ThrowTest() - { - Assert.Throws(CreateBlockChainService); - - static BlockChainService CreateBlockChainService() - { - var genesisOptions = new OptionsWrapper(new GenesisOptions()); - var storeOptions = new OptionsWrapper(new StoreOptions()); - var policyService = new PolicyService(); - var logger = new NullLoggerFactory().CreateLogger(); - return new BlockChainService( - genesisOptions: genesisOptions, - storeOptions: storeOptions, - policyService: policyService, - actionLoaderProviders: [], - logger: logger); - } - } - [Fact] public void Create_Test() { var services = new ServiceCollection(); services.AddLogging(configure => configure.AddProvider(NullLoggerProvider.Instance)); - services.AddOptionsConfigurator(); - services.AddOptionsConfigurator(); - services.AddOptionsConfigurator(); - services.AddOptionsValidator(); - services.AddOptionsValidator(); - services.AddOptionsValidator(); + services.AddOptions(); + services.AddSingleton, GenesisOptionsConfigurator>(); + services.AddOptions(); + services.AddSingleton, StoreOptionsConfigurator>(); + services.AddOptions(); + services.AddSingleton, SwarmOptionsConfigurator>(); + var serviceProvider = services.BuildServiceProvider(); var policyService = new PolicyService(); var logger = new NullLoggerFactory().CreateLogger(); @@ -64,12 +44,12 @@ public async Task BlockAppended_TestAsync() { var services = new ServiceCollection(); services.AddLogging(configure => configure.AddProvider(NullLoggerProvider.Instance)); - services.AddOptionsConfigurator(); - services.AddOptionsConfigurator(); - services.AddOptionsConfigurator(); - services.AddOptionsValidator(); - services.AddOptionsValidator(); - services.AddOptionsValidator(); + services.AddOptions(); + services.AddSingleton, GenesisOptionsConfigurator>(); + services.AddOptions(); + services.AddSingleton, StoreOptionsConfigurator>(); + services.AddOptions(); + services.AddSingleton, SwarmOptionsConfigurator>(); services.AddSingleton(); services.AddSingleton(); var serviceProvider = services.BuildServiceProvider(); diff --git a/sdk/node/Libplanet.Node.Tests/Services/NodeServiceTest.cs b/sdk/node/Libplanet.Node.Tests/Services/NodeServiceTest.cs index 4d7d9bc59bf..f6ad2056dcd 100644 --- a/sdk/node/Libplanet.Node.Tests/Services/NodeServiceTest.cs +++ b/sdk/node/Libplanet.Node.Tests/Services/NodeServiceTest.cs @@ -1,9 +1,9 @@ -using Libplanet.Node.Extensions; using Libplanet.Node.Options; using Libplanet.Node.Services; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; namespace Libplanet.Node.Tests.Services; @@ -73,12 +73,12 @@ private static ServiceCollection CreateServices() { var services = new ServiceCollection(); services.AddLogging(configure => configure.AddProvider(NullLoggerProvider.Instance)); - services.AddOptionsConfigurator(); - services.AddOptionsConfigurator(); - services.AddOptionsConfigurator(); - services.AddOptionsValidator(); - services.AddOptionsValidator(); - services.AddOptionsValidator(); + services.AddOptions(); + services.AddSingleton, GenesisOptionsConfigurator>(); + services.AddOptions(); + services.AddSingleton, StoreOptionsConfigurator>(); + services.AddOptions(); + services.AddSingleton, SwarmOptionsConfigurator>(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/sdk/node/Libplanet.Node/DependencyInjection/GrpcAttribute.cs b/sdk/node/Libplanet.Node/DependencyInjection/GrpcAttribute.cs deleted file mode 100644 index 4bca9c5e680..00000000000 --- a/sdk/node/Libplanet.Node/DependencyInjection/GrpcAttribute.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Libplanet.Node.DependencyInjection; - -[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] -public sealed class GrpcAttribute : Attribute -{ - public string Scope { get; set; } = string.Empty; -} diff --git a/sdk/node/Libplanet.Node/DependencyInjection/SingletonAttribute.cs b/sdk/node/Libplanet.Node/DependencyInjection/SingletonAttribute.cs deleted file mode 100644 index 2455c8a877a..00000000000 --- a/sdk/node/Libplanet.Node/DependencyInjection/SingletonAttribute.cs +++ /dev/null @@ -1,29 +0,0 @@ -#pragma warning disable SA1402 // File may only contain a single type - -namespace Libplanet.Node.DependencyInjection; - -[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] -public class SingletonAttribute : Attribute -{ - public SingletonAttribute(Type serviceType) - { - ServiceType = serviceType; - } - - public SingletonAttribute() - { - } - - public Type? ServiceType { get; } - - public string Scope { get; set; } = string.Empty; -} - -public sealed class SingletonAttribute : SingletonAttribute - where T : class -{ - public SingletonAttribute() - : base(typeof(T)) - { - } -} diff --git a/sdk/node/Libplanet.Node/Extensions/IEndpointRouteBuilderExtensions.cs b/sdk/node/Libplanet.Node/Extensions/IEndpointRouteBuilderExtensions.cs deleted file mode 100644 index 5146c171ec6..00000000000 --- a/sdk/node/Libplanet.Node/Extensions/IEndpointRouteBuilderExtensions.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Reflection; -using Libplanet.Node.DependencyInjection; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Routing; - -namespace Libplanet.Node.Extensions; - -public static class IEndpointRouteBuilderExtensions -{ - public static IEndpointRouteBuilder MapGrpcServiceFromDomain( - this IEndpointRouteBuilder @this) - => @this.MapGrpcServiceFromDomain(scope: string.Empty); - - public static IEndpointRouteBuilder MapGrpcServiceFromDomain( - this IEndpointRouteBuilder @this, string scope) - { - var types = ServiceUtility.GetTypes(typeof(GrpcAttribute), inherit: true); - var methodType = typeof(GrpcEndpointRouteBuilderExtensions); - var methodName = nameof(GrpcEndpointRouteBuilderExtensions.MapGrpcService); - var bindingFlags = BindingFlags.Public | BindingFlags.Static; - var methodInfo = methodType.GetMethod(methodName, bindingFlags) - ?? throw new InvalidOperationException($"Method '{methodName}' not found."); - foreach (var type in types) - { - var attributes = GetAttributes(type, scope); - foreach (var attribute in attributes) - { - var genericMethod = methodInfo.MakeGenericMethod(type); - genericMethod.Invoke(null, [@this]); - } - - static IEnumerable GetAttributes(Type type, string scope) - => Attribute.GetCustomAttributes(type, typeof(GrpcAttribute)) - .OfType() - .Where(item => item.Scope == scope); - } - - return @this; - } - - public static IEndpointRouteBuilder MapGrpcServiceFromDomain( - this IEndpointRouteBuilder @this, string[] scopes) - { - foreach (var scope in scopes) - { - MapGrpcServiceFromDomain(@this, scope); - } - - return @this; - } -} diff --git a/sdk/node/Libplanet.Node/Extensions/ServiceCollectionExtensions.cs b/sdk/node/Libplanet.Node/Extensions/ServiceCollectionExtensions.cs deleted file mode 100644 index e324d350809..00000000000 --- a/sdk/node/Libplanet.Node/Extensions/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,77 +0,0 @@ -using Libplanet.Node.DependencyInjection; -using Libplanet.Node.Options; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; - -namespace Libplanet.Node.Extensions; - -public static class ServiceCollectionExtensions -{ - public static IServiceCollection AddSingletonsFromDomain(this IServiceCollection @this) - => @this.AddSingletonsFromDomain(scope: string.Empty); - - public static IServiceCollection AddSingletonsFromDomain( - this IServiceCollection @this, string scope) - { - var types = ServiceUtility.GetTypes(typeof(SingletonAttribute), inherit: true); - foreach (var type in types) - { - var serviceTypes = GetAttributes(type, scope).Select(item => item.ServiceType); - foreach (var serviceType in serviceTypes) - { - @this.AddSingleton(serviceType ?? type, type); - } - - static IEnumerable GetAttributes(Type type, string scope) - => Attribute.GetCustomAttributes(type, typeof(SingletonAttribute)) - .OfType() - .Where(item => item.Scope == scope); - } - - return @this; - } - - public static IServiceCollection AddOptionsFromDomain( - this IServiceCollection @this, IConfiguration configuration) - => @this.AddOptionsFromDomain(configuration, scope: string.Empty); - - public static IServiceCollection AddOptionsFromDomain( - this IServiceCollection @this, IConfiguration configuration, string scope) - { - var types = ServiceUtility.GetTypes(typeof(OptionsAttribute), inherit: true); - foreach (var type in types) - { - var optionsNames = GetAttributes(type, scope).Select(item => item.Name); - foreach (var optionsName in optionsNames) - { - @this.AddOptions() - .Bind(configuration.GetSection(optionsName)) - .ValidateDataAnnotations(); - } - - static IEnumerable GetAttributes(Type type, string scope) - => Attribute.GetCustomAttributes(type, typeof(OptionsAttribute)) - .OfType() - .Where(item => item.Scope == scope); - } - - return @this; - } - - public static IServiceCollection AddOptionsConfigurator(this IServiceCollection @this) - where TO : OptionsBase - where TC : OptionsConfiguratorBase - { - var configuratorType = typeof(IConfigureOptions<>).MakeGenericType(typeof(TO)); - return @this.AddSingleton(configuratorType, typeof(TC)); - } - - public static IServiceCollection AddOptionsValidator(this IServiceCollection @this) - where TO : OptionsBase - where TV : OptionsValidatorBase - { - var validatorType = typeof(IValidateOptions<>).MakeGenericType(typeof(TO)); - return @this.AddSingleton(validatorType, typeof(TV)); - } -} diff --git a/sdk/node/Libplanet.Node/Options/GenesisOptions.cs b/sdk/node/Libplanet.Node/Options/GenesisOptions.cs index 130b666d2ba..aa22a6a7235 100644 --- a/sdk/node/Libplanet.Node/Options/GenesisOptions.cs +++ b/sdk/node/Libplanet.Node/Options/GenesisOptions.cs @@ -2,7 +2,6 @@ using Libplanet.Crypto; using Libplanet.Net; using Libplanet.Node.DataAnnotations; -using Libplanet.Node.DependencyInjection; namespace Libplanet.Node.Options; diff --git a/sdk/node/Libplanet.Node/Options/GenesisOptionsConfigurator.cs b/sdk/node/Libplanet.Node/Options/GenesisOptionsConfigurator.cs index e25e70f35fa..afbfda63e09 100644 --- a/sdk/node/Libplanet.Node/Options/GenesisOptionsConfigurator.cs +++ b/sdk/node/Libplanet.Node/Options/GenesisOptionsConfigurator.cs @@ -1,12 +1,10 @@ using Libplanet.Common; using Libplanet.Crypto; -using Libplanet.Node.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Libplanet.Node.Options; -[Singleton>] internal sealed class GenesisOptionsConfigurator( IOptions nodeOptions, ILogger logger) : OptionsConfiguratorBase diff --git a/sdk/node/Libplanet.Node/Options/GenesisOptionsValidator.cs b/sdk/node/Libplanet.Node/Options/GenesisOptionsValidator.cs deleted file mode 100644 index 42579803410..00000000000 --- a/sdk/node/Libplanet.Node/Options/GenesisOptionsValidator.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Libplanet.Node.DependencyInjection; -using Microsoft.Extensions.Options; - -namespace Libplanet.Node.Options; - -[Singleton>] -internal sealed class GenesisOptionsValidator : OptionsValidatorBase -{ - protected override void OnValidate(string? name, GenesisOptions options) - { - } -} diff --git a/sdk/node/Libplanet.Node/DependencyInjection/OptionsAttribute.cs b/sdk/node/Libplanet.Node/Options/OptionsAttribute.cs similarity index 62% rename from sdk/node/Libplanet.Node/DependencyInjection/OptionsAttribute.cs rename to sdk/node/Libplanet.Node/Options/OptionsAttribute.cs index 91c04d12b3f..4022720738b 100644 --- a/sdk/node/Libplanet.Node/DependencyInjection/OptionsAttribute.cs +++ b/sdk/node/Libplanet.Node/Options/OptionsAttribute.cs @@ -1,9 +1,7 @@ -namespace Libplanet.Node.DependencyInjection; +namespace Libplanet.Node.Options; [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public sealed class OptionsAttribute(string name) : Attribute { public string Name { get; } = name; - - public string Scope { get; set; } = string.Empty; } diff --git a/sdk/node/Libplanet.Node/Options/Schema/OptionsSchemaBuilder.cs b/sdk/node/Libplanet.Node/Options/Schema/OptionsSchemaBuilder.cs index dffddfda7c5..1eb48ca497e 100644 --- a/sdk/node/Libplanet.Node/Options/Schema/OptionsSchemaBuilder.cs +++ b/sdk/node/Libplanet.Node/Options/Schema/OptionsSchemaBuilder.cs @@ -1,6 +1,5 @@ using System.ComponentModel; using System.Reflection; -using Libplanet.Node.DependencyInjection; using NJsonSchema; using NJsonSchema.Generation; diff --git a/sdk/node/Libplanet.Node/Options/SeedOptions.cs b/sdk/node/Libplanet.Node/Options/SeedOptions.cs index 6a36d577a5f..84b84fb924b 100644 --- a/sdk/node/Libplanet.Node/Options/SeedOptions.cs +++ b/sdk/node/Libplanet.Node/Options/SeedOptions.cs @@ -1,12 +1,9 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; using Libplanet.Node.DataAnnotations; -using Libplanet.Node.DependencyInjection; namespace Libplanet.Node.Options; -[Options(BlocksyncSeed, Scope = "Seed")] -[Options(ConsensusSeed, Scope = "Seed")] public sealed class SeedOptions : OptionsBase { public const string BlocksyncSeed = nameof(BlocksyncSeed); diff --git a/sdk/node/Libplanet.Node/Options/SeedOptionsConfigurator.cs b/sdk/node/Libplanet.Node/Options/SeedOptionsConfigurator.cs index 10854a8289d..fae270f1d00 100644 --- a/sdk/node/Libplanet.Node/Options/SeedOptionsConfigurator.cs +++ b/sdk/node/Libplanet.Node/Options/SeedOptionsConfigurator.cs @@ -1,13 +1,9 @@ using Libplanet.Common; using Libplanet.Crypto; -using Libplanet.Node.DependencyInjection; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; namespace Libplanet.Node.Options; -[Singleton>(Scope = "Seed")] -[Singleton>(Scope = "Seed")] public sealed class SeedOptionsConfigurator( ILogger logger) : ConfigureNamedOptionsBase diff --git a/sdk/node/Libplanet.Node/Options/SeedOptionsValidator.cs b/sdk/node/Libplanet.Node/Options/SeedOptionsValidator.cs deleted file mode 100644 index e45623e974d..00000000000 --- a/sdk/node/Libplanet.Node/Options/SeedOptionsValidator.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Libplanet.Node.DependencyInjection; -using Microsoft.Extensions.Options; - -namespace Libplanet.Node.Options; - -[Singleton>] -internal sealed class SeedOptionsValidator : OptionsValidatorBase -{ - protected override void OnValidate(string? name, SeedOptions options) - { - } -} diff --git a/sdk/node/Libplanet.Node/Options/SoloOptions.cs b/sdk/node/Libplanet.Node/Options/SoloOptions.cs new file mode 100644 index 00000000000..f275a276de0 --- /dev/null +++ b/sdk/node/Libplanet.Node/Options/SoloOptions.cs @@ -0,0 +1,18 @@ +using System.ComponentModel; +using Libplanet.Node.DataAnnotations; + +namespace Libplanet.Node.Options; + +[Options(Position)] +public class SoloOptions : OptionsBase +{ + public const string Position = "Solo"; + + public bool IsEnabled { get; set; } + + public long BlockInterval { get; set; } = 4000; + + [PrivateKey] + [Description("The private key of the node.")] + public string PrivateKey { get; set; } = string.Empty; +} diff --git a/sdk/node/Libplanet.Node/Options/SoloOptionsConfigurator.cs b/sdk/node/Libplanet.Node/Options/SoloOptionsConfigurator.cs new file mode 100644 index 00000000000..a388a7c9531 --- /dev/null +++ b/sdk/node/Libplanet.Node/Options/SoloOptionsConfigurator.cs @@ -0,0 +1,21 @@ +using Libplanet.Common; +using Libplanet.Crypto; +using Microsoft.Extensions.Logging; + +namespace Libplanet.Node.Options; + +internal sealed class SoloOptionsConfigurator( + ILogger logger) + : OptionsConfiguratorBase +{ + protected override void OnConfigure(SoloOptions options) + { + if (options.PrivateKey == string.Empty) + { + options.PrivateKey = ByteUtil.Hex(new PrivateKey().ByteArray); + logger.LogWarning( + "Node's private key is not set. A new private key is generated: {PrivateKey}", + options.PrivateKey); + } + } +} diff --git a/sdk/node/Libplanet.Node/Options/SoloProposeOption.cs b/sdk/node/Libplanet.Node/Options/SoloProposeOption.cs deleted file mode 100644 index 5da8730d07c..00000000000 --- a/sdk/node/Libplanet.Node/Options/SoloProposeOption.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Libplanet.Node.Options; - -public class SoloProposeOption -{ - public const string Position = "Solo"; - - public bool Enable { get; set; } = false; - - public long BlockInterval { get; set; } = 4000; - - public string? PrivateKey { get; set; } -} diff --git a/sdk/node/Libplanet.Node/Options/StoreOptions.cs b/sdk/node/Libplanet.Node/Options/StoreOptions.cs index d1673d2de65..8bc5991cddd 100644 --- a/sdk/node/Libplanet.Node/Options/StoreOptions.cs +++ b/sdk/node/Libplanet.Node/Options/StoreOptions.cs @@ -1,5 +1,4 @@ using System.ComponentModel; -using Libplanet.Node.DependencyInjection; namespace Libplanet.Node.Options; diff --git a/sdk/node/Libplanet.Node/Options/StoreOptionsConfigurator.cs b/sdk/node/Libplanet.Node/Options/StoreOptionsConfigurator.cs index 88b196a5fa2..a44822c449e 100644 --- a/sdk/node/Libplanet.Node/Options/StoreOptionsConfigurator.cs +++ b/sdk/node/Libplanet.Node/Options/StoreOptionsConfigurator.cs @@ -1,10 +1,7 @@ -using Libplanet.Node.DependencyInjection; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; namespace Libplanet.Node.Options; -[Singleton>] internal sealed class StoreOptionsConfigurator(ILogger logger) : OptionsConfiguratorBase { diff --git a/sdk/node/Libplanet.Node/Options/StoreOptionsValidator.cs b/sdk/node/Libplanet.Node/Options/StoreOptionsValidator.cs deleted file mode 100644 index cc04acdfb3a..00000000000 --- a/sdk/node/Libplanet.Node/Options/StoreOptionsValidator.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Libplanet.Node.DependencyInjection; -using Microsoft.Extensions.Options; - -namespace Libplanet.Node.Options; - -[Singleton>] -internal sealed class StoreOptionsValidator : OptionsValidatorBase -{ - protected override void OnValidate(string? name, StoreOptions options) - { - } -} diff --git a/sdk/node/Libplanet.Node/Options/SwarmOptions.cs b/sdk/node/Libplanet.Node/Options/SwarmOptions.cs index 8c431281e05..9f78121e738 100644 --- a/sdk/node/Libplanet.Node/Options/SwarmOptions.cs +++ b/sdk/node/Libplanet.Node/Options/SwarmOptions.cs @@ -1,14 +1,15 @@ using System.ComponentModel; using Libplanet.Node.DataAnnotations; -using Libplanet.Node.DependencyInjection; namespace Libplanet.Node.Options; -[Options(Position, Scope = "Swarm")] +[Options(Position)] public sealed class SwarmOptions : OptionsBase { public const string Position = "Swarm"; + public bool IsEnabled { get; set; } + [PrivateKey] [Description("The private key of the node.")] public string PrivateKey { get; set; } = string.Empty; diff --git a/sdk/node/Libplanet.Node/Options/SwarmOptionsConfigurator.cs b/sdk/node/Libplanet.Node/Options/SwarmOptionsConfigurator.cs index 0f9ed998b59..158d8fc2ca8 100644 --- a/sdk/node/Libplanet.Node/Options/SwarmOptionsConfigurator.cs +++ b/sdk/node/Libplanet.Node/Options/SwarmOptionsConfigurator.cs @@ -1,12 +1,9 @@ using Libplanet.Common; using Libplanet.Crypto; -using Libplanet.Node.DependencyInjection; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; namespace Libplanet.Node.Options; -[Singleton>(Scope = "Swarm")] internal sealed class SwarmOptionsConfigurator( ILogger logger) : OptionsConfiguratorBase diff --git a/sdk/node/Libplanet.Node/Options/SwarmOptionsValidator.cs b/sdk/node/Libplanet.Node/Options/SwarmOptionsValidator.cs index 771065eda6c..3af05c4fbbf 100644 --- a/sdk/node/Libplanet.Node/Options/SwarmOptionsValidator.cs +++ b/sdk/node/Libplanet.Node/Options/SwarmOptionsValidator.cs @@ -1,12 +1,21 @@ -using Libplanet.Node.DependencyInjection; using Microsoft.Extensions.Options; namespace Libplanet.Node.Options; -[Singleton>] -internal sealed class SwarmOptionsValidator : OptionsValidatorBase +internal sealed class SwarmOptionsValidator( + IOptions soloOptions) + : OptionsValidatorBase { protected override void OnValidate(string? name, SwarmOptions options) { + var soloOptionsValue = soloOptions.Value; + if (options.IsEnabled && soloOptionsValue.IsEnabled) + { + throw new OptionsValidationException( + optionsName: name ?? string.Empty, + optionsType: typeof(SwarmOptions), + failureMessages: [ + "Both Swarm and SoloPropose cannot be enabled at the same time."]); + } } } diff --git a/sdk/node/Libplanet.Node/Options/ValidatorOptions.cs b/sdk/node/Libplanet.Node/Options/ValidatorOptions.cs index dcbd14c0d04..e203cca786f 100644 --- a/sdk/node/Libplanet.Node/Options/ValidatorOptions.cs +++ b/sdk/node/Libplanet.Node/Options/ValidatorOptions.cs @@ -1,14 +1,16 @@ using System.ComponentModel; using Libplanet.Node.DataAnnotations; -using Libplanet.Node.DependencyInjection; namespace Libplanet.Node.Options; -[Options(Position, Scope = "Validator")] +[Options(Position)] public sealed class ValidatorOptions : OptionsBase { public const string Position = "Validator"; + [DefaultValue(true)] + public bool IsEnabled { get; set; } + [DnsEndPoint] public string EndPoint { get; set; } = string.Empty; diff --git a/sdk/node/Libplanet.Node/Options/ValidatorOptionsConfigurator.cs b/sdk/node/Libplanet.Node/Options/ValidatorOptionsConfigurator.cs index b7bc3cb9292..6a04c0c9852 100644 --- a/sdk/node/Libplanet.Node/Options/ValidatorOptionsConfigurator.cs +++ b/sdk/node/Libplanet.Node/Options/ValidatorOptionsConfigurator.cs @@ -1,10 +1,8 @@ -using Libplanet.Node.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Libplanet.Node.Options; -[Singleton>(Scope = "Validator")] internal sealed class ValidatorOptionsConfigurator( ILogger logger) : OptionsConfiguratorBase diff --git a/sdk/node/Libplanet.Node/Options/ValidatorOptionsValidator.cs b/sdk/node/Libplanet.Node/Options/ValidatorOptionsValidator.cs index 25e9538dbb5..fe3d11b0f49 100644 --- a/sdk/node/Libplanet.Node/Options/ValidatorOptionsValidator.cs +++ b/sdk/node/Libplanet.Node/Options/ValidatorOptionsValidator.cs @@ -1,12 +1,21 @@ -using Libplanet.Node.DependencyInjection; using Microsoft.Extensions.Options; namespace Libplanet.Node.Options; -[Singleton>] -internal sealed class ValidatorOptionsValidator : OptionsValidatorBase +internal sealed class ValidatorOptionsValidator( + IOptions swarmOptions) + : OptionsValidatorBase { protected override void OnValidate(string? name, ValidatorOptions options) { + var swarmOptionsValue = swarmOptions.Value; + if (options.IsEnabled && !swarmOptionsValue.IsEnabled) + { + throw new OptionsValidationException( + optionsName: name ?? string.Empty, + optionsType: typeof(ValidatorOptions), + failureMessages: [ + "Validator cannot be enabled when Swarm is disabled."]); + } } } diff --git a/sdk/node/Libplanet.Node/DependencyInjection/ServiceUtility.cs b/sdk/node/Libplanet.Node/ServiceUtility.cs similarity index 92% rename from sdk/node/Libplanet.Node/DependencyInjection/ServiceUtility.cs rename to sdk/node/Libplanet.Node/ServiceUtility.cs index 68e05966c46..1b6a1345dde 100644 --- a/sdk/node/Libplanet.Node/DependencyInjection/ServiceUtility.cs +++ b/sdk/node/Libplanet.Node/ServiceUtility.cs @@ -1,6 +1,6 @@ using System.Reflection; -namespace Libplanet.Node.DependencyInjection; +namespace Libplanet.Node; public static class ServiceUtility { diff --git a/sdk/node/Libplanet.Node/Services/BlockChainService.cs b/sdk/node/Libplanet.Node/Services/BlockChainService.cs index 231fce95973..ef2f85c797a 100644 --- a/sdk/node/Libplanet.Node/Services/BlockChainService.cs +++ b/sdk/node/Libplanet.Node/Services/BlockChainService.cs @@ -11,7 +11,6 @@ using Libplanet.Blockchain.Renderers; using Libplanet.Common; using Libplanet.Crypto; -using Libplanet.Node.DependencyInjection; using Libplanet.Node.Options; using Libplanet.RocksDBStore; using Libplanet.Store; @@ -24,8 +23,6 @@ namespace Libplanet.Node.Services; -[Singleton] -[Singleton] internal sealed class BlockChainService : IBlockChainService, IActionRenderer { private readonly SynchronizationContext _synchronizationContext; @@ -44,8 +41,8 @@ public BlockChainService( _synchronizationContext = SynchronizationContext.Current ?? new(); _logger = logger; _blockChain = CreateBlockChain( - genesisOptions: genesisOptions.Value.Verify(), - storeOptions: storeOptions.Value.Verify(), + genesisOptions: genesisOptions.Value, + storeOptions: storeOptions.Value, stagePolicy: policyService.StagePolicy, renderers: [this], actionLoaders: [.. actionLoaderProviders.Select(item => item.GetActionLoader())]); diff --git a/sdk/node/Libplanet.Node/Services/BlocksyncSeedService.cs b/sdk/node/Libplanet.Node/Services/BlocksyncSeedService.cs deleted file mode 100644 index 0f3759be6af..00000000000 --- a/sdk/node/Libplanet.Node/Services/BlocksyncSeedService.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Libplanet.Node.DependencyInjection; -using Libplanet.Node.Options; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace Libplanet.Node.Services; - -[Singleton(Scope = "Seed")] -[Singleton(Scope = "Seed")] -internal sealed class BlocksyncSeedService( - IOptionsMonitor seedOptionsMonitor, - ILogger logger) - : SeedServiceBase(seedOptionsMonitor.Get(SeedOptions.BlocksyncSeed), logger), - IBlocksyncSeedService -{ -} diff --git a/sdk/node/Libplanet.Node/Services/ConsensusSeedService.cs b/sdk/node/Libplanet.Node/Services/ConsensusSeedService.cs deleted file mode 100644 index ea5134e884f..00000000000 --- a/sdk/node/Libplanet.Node/Services/ConsensusSeedService.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Libplanet.Node.DependencyInjection; -using Libplanet.Node.Options; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace Libplanet.Node.Services; - -[Singleton(Scope = "Seed")] -[Singleton(Scope = "Seed")] -internal sealed class ConsensusSeedService( - IOptionsMonitor seedOptionsMonitor, - ILogger logger) - : SeedServiceBase(seedOptionsMonitor.Get(SeedOptions.ConsensusSeed), logger), - IConsensusSeedService -{ -} diff --git a/sdk/node/Libplanet.Node/Services/PolicyService.cs b/sdk/node/Libplanet.Node/Services/PolicyService.cs index a27761eb82f..d691877594b 100644 --- a/sdk/node/Libplanet.Node/Services/PolicyService.cs +++ b/sdk/node/Libplanet.Node/Services/PolicyService.cs @@ -1,9 +1,7 @@ using Libplanet.Blockchain.Policies; -using Libplanet.Node.DependencyInjection; namespace Libplanet.Node.Services; -[Singleton] public class PolicyService { public IStagePolicy StagePolicy { get; } = new VolatileStagePolicy(); diff --git a/sdk/node/Libplanet.Node/Services/ReadChainService.cs b/sdk/node/Libplanet.Node/Services/ReadChainService.cs index 5bd7b1f160f..33e62130992 100644 --- a/sdk/node/Libplanet.Node/Services/ReadChainService.cs +++ b/sdk/node/Libplanet.Node/Services/ReadChainService.cs @@ -1,10 +1,8 @@ -using Libplanet.Node.DependencyInjection; using Libplanet.Types.Blocks; namespace Libplanet.Node.Services; -[Singleton] -internal sealed class ReadChainService(BlockChainService blockChainService) : IReadChainService +internal sealed class ReadChainService(IBlockChainService blockChainService) : IReadChainService { public Block Tip => blockChainService.BlockChain.Tip; diff --git a/sdk/node/Libplanet.Node/Services/SeedServiceBase.cs b/sdk/node/Libplanet.Node/Services/SeedServiceBase.cs index 3ab3ab2bd80..0af571379d2 100644 --- a/sdk/node/Libplanet.Node/Services/SeedServiceBase.cs +++ b/sdk/node/Libplanet.Node/Services/SeedServiceBase.cs @@ -8,7 +8,7 @@ namespace Libplanet.Node.Services; internal class SeedServiceBase(SeedOptions seedOptions, ILogger logger) : IHostedService { - private readonly Seed _seed = new(seedOptions.Verify()); + private readonly Seed _seed = new(seedOptions); public BoundPeer BoundPeer => _seed.BoundPeer; diff --git a/sdk/node/Libplanet.Node/Services/SoloProposeService.cs b/sdk/node/Libplanet.Node/Services/SoloProposeService.cs index 80a66a41f75..4898a83665d 100644 --- a/sdk/node/Libplanet.Node/Services/SoloProposeService.cs +++ b/sdk/node/Libplanet.Node/Services/SoloProposeService.cs @@ -15,9 +15,9 @@ internal sealed class SoloProposeService : BackgroundService private readonly ILogger _logger; public SoloProposeService( - BlockChainService blockChainService, + IBlockChainService blockChainService, ILogger logger, - IOptions soloProposeOption) + IOptions soloProposeOption) { _blockChain = blockChainService.BlockChain; var options = soloProposeOption.Value; diff --git a/sdk/node/Libplanet.Node/Services/SwarmService.cs b/sdk/node/Libplanet.Node/Services/SwarmService.cs index 18484f1cb8e..7b44889b641 100644 --- a/sdk/node/Libplanet.Node/Services/SwarmService.cs +++ b/sdk/node/Libplanet.Node/Services/SwarmService.cs @@ -4,7 +4,6 @@ using Libplanet.Net; using Libplanet.Net.Consensus; using Libplanet.Net.Transports; -using Libplanet.Node.DependencyInjection; using Libplanet.Node.Options; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -13,8 +12,6 @@ namespace Libplanet.Node.Services; -[Singleton(Scope = "Swarm")] -[Singleton(Scope = "Swarm")] internal sealed class SwarmService( IServiceProvider serviceProvider, IOptions options, diff --git a/sdk/node/Libplanet.Node/Services/TransactionService.cs b/sdk/node/Libplanet.Node/Services/TransactionService.cs index 496254cd8d6..ff2433e255d 100644 --- a/sdk/node/Libplanet.Node/Services/TransactionService.cs +++ b/sdk/node/Libplanet.Node/Services/TransactionService.cs @@ -1,11 +1,9 @@ using Libplanet.Blockchain; -using Libplanet.Node.DependencyInjection; using Libplanet.Types.Tx; namespace Libplanet.Node.Services; -[Singleton] -internal sealed class TransactionService(BlockChainService blockChainService) +internal sealed class TransactionService(IBlockChainService blockChainService) { private readonly BlockChain _blockChain = blockChainService.BlockChain; diff --git a/sdk/node/Libplanet.Node/Services/ValidatorService.cs b/sdk/node/Libplanet.Node/Services/ValidatorService.cs index 8c804e5bfee..7a59338f68d 100644 --- a/sdk/node/Libplanet.Node/Services/ValidatorService.cs +++ b/sdk/node/Libplanet.Node/Services/ValidatorService.cs @@ -1,8 +1,5 @@ -using Libplanet.Node.DependencyInjection; - namespace Libplanet.Node.Services; -[Singleton(Scope = "Validator")] internal sealed class ValidatorService : IValidatorService { }