diff --git a/src/client/LibplanetConsole.Client.Executable/Application.cs b/src/client/LibplanetConsole.Client.Executable/Application.cs index 7c22a225..6b4d01ee 100644 --- a/src/client/LibplanetConsole.Client.Executable/Application.cs +++ b/src/client/LibplanetConsole.Client.Executable/Application.cs @@ -17,7 +17,7 @@ internal sealed class Application public Application(ApplicationOptions options, object[] instances) { - var (_, port) = EndPointUtility.GetHostAndPort(options.EndPoint); + var port = options.Port; var services = _builder.Services; foreach (var instance in instances) { @@ -27,6 +27,7 @@ public Application(ApplicationOptions options, object[] instances) _builder.WebHost.ConfigureKestrel(options => { options.ListenLocalhost(port, o => o.Protocols = HttpProtocols.Http2); + options.ListenLocalhost(port + 1, o => o.Protocols = HttpProtocols.Http1AndHttp2); }); if (options.LogPath != string.Empty) diff --git a/src/client/LibplanetConsole.Client.Executable/ApplicationSettings.cs b/src/client/LibplanetConsole.Client.Executable/ApplicationSettings.cs index aff7d370..08578665 100644 --- a/src/client/LibplanetConsole.Client.Executable/ApplicationSettings.cs +++ b/src/client/LibplanetConsole.Client.Executable/ApplicationSettings.cs @@ -12,10 +12,10 @@ namespace LibplanetConsole.Client.Executable; internal sealed record class ApplicationSettings { [CommandProperty] - [CommandSummary("Indicates the EndPoint on which the client will run. " + - "If omitted, a random endpoint is used.")] - [EndPoint] - public string EndPoint { get; init; } = string.Empty; + [CommandSummary("Indicates the port on which the client will run. " + + "If omitted, a random port is used.")] + [NonNegative] + public int Port { get; init; } [CommandProperty] [CommandSummary("Indicates the private key of the client. " + @@ -47,9 +47,9 @@ internal sealed record class ApplicationSettings public ApplicationOptions ToOptions() { - var endPoint = EndPointUtility.ParseOrNext(EndPoint); + var port = Port; var privateKey = PrivateKeyUtility.ParseOrRandom(PrivateKey); - return new ApplicationOptions(endPoint, privateKey) + return new ApplicationOptions(port, privateKey) { ParentProcessId = ParentProcessId, NodeEndPoint = EndPointUtility.ParseOrDefault(NodeEndPoint), diff --git a/src/client/LibplanetConsole.Client.Executable/EntryCommands/InitializeCommand.cs b/src/client/LibplanetConsole.Client.Executable/EntryCommands/InitializeCommand.cs index c34affca..edc5876e 100644 --- a/src/client/LibplanetConsole.Client.Executable/EntryCommands/InitializeCommand.cs +++ b/src/client/LibplanetConsole.Client.Executable/EntryCommands/InitializeCommand.cs @@ -27,10 +27,10 @@ public InitializeCommand() public string PrivateKey { get; init; } = string.Empty; [CommandProperty] - [CommandSummary("The endpoint of the client. " + + [CommandSummary("The port of the client. " + "If omitted, a random endpoint is used.")] - [EndPoint] - public string EndPoint { get; set; } = string.Empty; + [NonNegative] + public int Port { get; set; } [CommandProperty] [CommandSummary("Indicates the file path to save logs.")] @@ -44,12 +44,12 @@ public InitializeCommand() protected override void OnExecute() { var outputPath = Path.GetFullPath(OutputPath); - var endPoint = EndPointUtility.ParseOrNext(EndPoint); + var port = Port == 0 ? PortUtility.NextPort() : Port; var privateKey = PrivateKeyUtility.ParseOrRandom(PrivateKey); var logPath = Path.Combine(outputPath, LogPath.Fallback("log")); var repository = new Repository { - EndPoint = endPoint, + Port = port, PrivateKey = privateKey, LogPath = logPath, }; diff --git a/src/client/LibplanetConsole.Client.Executable/Repository.cs b/src/client/LibplanetConsole.Client.Executable/Repository.cs index e0312730..30b5e003 100644 --- a/src/client/LibplanetConsole.Client.Executable/Repository.cs +++ b/src/client/LibplanetConsole.Client.Executable/Repository.cs @@ -11,7 +11,7 @@ public sealed record class Repository public const string SettingsFileName = "client-settings.json"; public const string SettingsSchemaFileName = "client-settings-schema.json"; - public required EndPoint EndPoint { get; init; } + public required int Port { get; init; } public required PrivateKey PrivateKey { get; init; } @@ -19,8 +19,6 @@ public sealed record class Repository public string LogPath { get; init; } = string.Empty; - public string Source { get; private set; } = string.Empty; - public static Repository Load(string settingsPath) { if (Path.IsPathRooted(settingsPath) is false) @@ -37,10 +35,9 @@ public static Repository Load(string settingsPath) return new() { - EndPoint = EndPointUtility.Parse(applicationSettings.EndPoint), + Port = applicationSettings.Port, PrivateKey = new PrivateKey(applicationSettings.PrivateKey), LogPath = Path.GetFullPath(applicationSettings.LogPath, directoryName), - Source = settingsPath, NodeEndPoint = EndPointUtility.ParseOrDefault(applicationSettings.NodeEndPoint), }; } @@ -80,7 +77,7 @@ public dynamic Save(string repositoryPath) Schema = SettingsSchemaFileName, Application = new ApplicationSettings { - EndPoint = EndPointUtility.ToString(EndPoint), + Port = Port, PrivateKey = PrivateKeyUtility.ToString(privateKey), LogPath = GetRelativePathFromDirectory(repositoryPath, LogPath), NodeEndPoint = EndPointUtility.ToString(NodeEndPoint), diff --git a/src/client/LibplanetConsole.Client.Executable/Tracers/ClientEventTracer.cs b/src/client/LibplanetConsole.Client.Executable/Tracers/ClientEventTracer.cs index 1a1be3e8..0913c950 100644 --- a/src/client/LibplanetConsole.Client.Executable/Tracers/ClientEventTracer.cs +++ b/src/client/LibplanetConsole.Client.Executable/Tracers/ClientEventTracer.cs @@ -30,14 +30,14 @@ void IDisposable.Dispose() private void Client_Started(object? sender, EventArgs e) { - var endPoint = _options.EndPoint; + var endPoint = _options.Port; var message = $"BlockChain has been started.: {endPoint}"; Console.Out.WriteColoredLine(message, TerminalColorType.BrightGreen); } private void Client_Stopped(object? sender, EventArgs e) { - var endPoint = _options.EndPoint; + var endPoint = _options.Port; var message = $"BlockChain has been stopped.: {endPoint}"; Console.Out.WriteColoredLine(message, TerminalColorType.BrightGreen); } diff --git a/src/client/LibplanetConsole.Client/ApplicationInfo.cs b/src/client/LibplanetConsole.Client/ApplicationInfo.cs index e804b006..2cffd325 100644 --- a/src/client/LibplanetConsole.Client/ApplicationInfo.cs +++ b/src/client/LibplanetConsole.Client/ApplicationInfo.cs @@ -5,8 +5,7 @@ namespace LibplanetConsole.Client; public readonly record struct ApplicationInfo { - [JsonConverter(typeof(EndPointJsonConverter))] - public required EndPoint EndPoint { get; init; } + public required int Port { get; init; } [JsonConverter(typeof(EndPointJsonConverter))] public EndPoint? NodeEndPoint { get; init; } diff --git a/src/client/LibplanetConsole.Client/ApplicationInfoProvider.cs b/src/client/LibplanetConsole.Client/ApplicationInfoProvider.cs index bdcf2b57..7a15b1fb 100644 --- a/src/client/LibplanetConsole.Client/ApplicationInfoProvider.cs +++ b/src/client/LibplanetConsole.Client/ApplicationInfoProvider.cs @@ -13,7 +13,7 @@ public ApplicationInfoProvider(ApplicationOptions options) { _info = new() { - EndPoint = options.EndPoint, + Port = options.Port, NodeEndPoint = options.NodeEndPoint, LogPath = options.LogPath, }; diff --git a/src/client/LibplanetConsole.Client/ApplicationOptions.cs b/src/client/LibplanetConsole.Client/ApplicationOptions.cs index 7e69c6b8..663b686e 100644 --- a/src/client/LibplanetConsole.Client/ApplicationOptions.cs +++ b/src/client/LibplanetConsole.Client/ApplicationOptions.cs @@ -2,13 +2,13 @@ namespace LibplanetConsole.Client; public sealed record class ApplicationOptions { - public ApplicationOptions(EndPoint endPoint, PrivateKey privateKey) + public ApplicationOptions(int port, PrivateKey privateKey) { - EndPoint = endPoint; + Port = port; PrivateKey = privateKey; } - public EndPoint EndPoint { get; } + public int Port { get; } public PrivateKey PrivateKey { get; } diff --git a/src/common/LibplanetConsole.Common/EndPointUtility.cs b/src/common/LibplanetConsole.Common/EndPointUtility.cs index 2c5c46ca..1309513f 100644 --- a/src/common/LibplanetConsole.Common/EndPointUtility.cs +++ b/src/common/LibplanetConsole.Common/EndPointUtility.cs @@ -58,6 +58,14 @@ public static (string Host, int Port) GetHostAndPort(EndPoint endPoint) }; } + public static string GetHost(EndPoint endPoint) + => GetHostAndPort(endPoint).Host; + + public static int GetPort(EndPoint endPoint) + => GetHostAndPort(endPoint).Port; + + public static DnsEndPoint GetLocalHost(int port) => new("localhost", port); + public static string ToString(EndPoint? endPoint) { if (endPoint is DnsEndPoint dnsEndPoint) diff --git a/src/common/LibplanetConsole.Common/PortUtility.cs b/src/common/LibplanetConsole.Common/PortUtility.cs index 874931d5..a63c6bc7 100644 --- a/src/common/LibplanetConsole.Common/PortUtility.cs +++ b/src/common/LibplanetConsole.Common/PortUtility.cs @@ -8,7 +8,7 @@ public static class PortUtility private static readonly List UsedPortList = []; private static readonly object LockObject = new(); - public static int GetPort() + public static int NextPort() { lock (LockObject) { diff --git a/src/common/LibplanetConsole.Seed/SeedNode.cs b/src/common/LibplanetConsole.Seed/SeedNode.cs index d0f1a2f0..19e7ea0a 100644 --- a/src/common/LibplanetConsole.Seed/SeedNode.cs +++ b/src/common/LibplanetConsole.Seed/SeedNode.cs @@ -5,6 +5,7 @@ using Libplanet.Net.Transports; using LibplanetConsole.Common; using Serilog; +using static LibplanetConsole.Common.EndPointUtility; namespace LibplanetConsole.Seed; @@ -30,7 +31,7 @@ public static readonly AppProtocolVersion AppProtocolVersion public PeerCollection Peers { get; } = new(seedOptions); public BoundPeer BoundPeer { get; } = new( - seedOptions.PrivateKey.PublicKey, (DnsEndPoint)seedOptions.EndPoint); + seedOptions.PrivateKey.PublicKey, GetLocalHost(seedOptions.Port)); public async Task StartAsync(CancellationToken cancellationToken) { @@ -107,8 +108,8 @@ private static async Task CreateTransport(SeedOptions seedOption AppProtocolVersion = appProtocolVersion, TrustedAppProtocolVersionSigners = [], }; - var (host, port) = EndPointUtility.GetHostAndPort(seedOptions.EndPoint); - var hostOptions = new HostOptions(host, [], port); + var port = seedOptions.Port; + var hostOptions = new HostOptions("localhost", [], port); return await NetMQTransport.Create(privateKey, appProtocolVersionOptions, hostOptions); } diff --git a/src/common/LibplanetConsole.Seed/SeedOptions.cs b/src/common/LibplanetConsole.Seed/SeedOptions.cs index c25b0f4b..1e9ac5d1 100644 --- a/src/common/LibplanetConsole.Seed/SeedOptions.cs +++ b/src/common/LibplanetConsole.Seed/SeedOptions.cs @@ -1,12 +1,10 @@ -using System.Net; - -namespace LibplanetConsole.Seed; +namespace LibplanetConsole.Seed; public sealed record class SeedOptions { public required PrivateKey PrivateKey { get; init; } - public required EndPoint EndPoint { get; init; } + public required int Port { get; init; } public TimeSpan RefreshInterval { get; init; } = TimeSpan.FromSeconds(5); diff --git a/src/console/LibplanetConsole.Console.Executable/Application.cs b/src/console/LibplanetConsole.Console.Executable/Application.cs index ed5270c0..73eb9816 100644 --- a/src/console/LibplanetConsole.Console.Executable/Application.cs +++ b/src/console/LibplanetConsole.Console.Executable/Application.cs @@ -21,7 +21,7 @@ internal sealed class Application public Application(ApplicationOptions options, object[] instances) { - var (_, port) = EndPointUtility.GetHostAndPort(options.EndPoint); + var port = options.Port; var services = _builder.Services; foreach (var instance in instances) @@ -32,6 +32,7 @@ public Application(ApplicationOptions options, object[] instances) _builder.WebHost.ConfigureKestrel(options => { options.ListenLocalhost(port, o => o.Protocols = HttpProtocols.Http2); + options.ListenLocalhost(port + 1, o => o.Protocols = HttpProtocols.Http1AndHttp2); }); if (options.LogPath != string.Empty) diff --git a/src/console/LibplanetConsole.Console.Executable/ApplicationSettings.cs b/src/console/LibplanetConsole.Console.Executable/ApplicationSettings.cs index a685af2e..4964a35e 100644 --- a/src/console/LibplanetConsole.Console.Executable/ApplicationSettings.cs +++ b/src/console/LibplanetConsole.Console.Executable/ApplicationSettings.cs @@ -2,9 +2,9 @@ using System.Text.Json.Serialization; using JSSoft.Commands; using LibplanetConsole.Common; -using LibplanetConsole.Common.DataAnnotations; using LibplanetConsole.DataAnnotations; using LibplanetConsole.Framework; +using static LibplanetConsole.Common.EndPointUtility; namespace LibplanetConsole.Console.Executable; @@ -12,10 +12,10 @@ namespace LibplanetConsole.Console.Executable; internal sealed record class ApplicationSettings { [CommandProperty] - [CommandSummary("The endpoint of the libplanet-console. " + - "If omitted, a random endpoint is used.")] - [EndPoint] - public string EndPoint { get; init; } = string.Empty; + [CommandSummary("The port of the libplanet-console. " + + "If omitted, a random port is used.")] + [NonNegative] + public int Port { get; init; } #if DEBUG [CommandProperty(InitValue = 1)] @@ -91,12 +91,13 @@ internal sealed record class ApplicationSettings public ApplicationOptions ToOptions() { - var endPoint = EndPointUtility.ParseOrNext(EndPoint); + var port = Port == 0 ? PortUtility.NextPort() : Port; + var endPoint = GetLocalHost(port); var nodeOptions = GetNodeOptions(endPoint, GetNodes()); var clientOptions = GetClientOptions(nodeOptions, GetClients()); - var repository = new Repository(endPoint, nodeOptions, clientOptions); + var repository = new Repository(port, nodeOptions, clientOptions); var genesis = TryGetGenesis(out var g) == true ? g : repository.Genesis; - return new ApplicationOptions(endPoint) + return new ApplicationOptions(port) { LogPath = GetFullPath(LogPath), Nodes = repository.Nodes, @@ -133,7 +134,7 @@ private static NodeOptions[] GetNodeOptions( { return [.. nodePrivateKeys.Select(key => new NodeOptions { - EndPoint = EndPointUtility.NextEndPoint(), + EndPoint = NextEndPoint(), PrivateKey = key, SeedEndPoint = endPoint, })]; @@ -144,7 +145,7 @@ private static ClientOptions[] GetClientOptions( { return [.. clientPrivateKeys.Select(key => new ClientOptions { - EndPoint = EndPointUtility.NextEndPoint(), + EndPoint = NextEndPoint(), NodeEndPoint = Random(nodeOptions).EndPoint, PrivateKey = key, })]; diff --git a/src/console/LibplanetConsole.Console.Executable/ClientRepositoryProcess.cs b/src/console/LibplanetConsole.Console.Executable/ClientRepositoryProcess.cs index 68e2a491..ea5c3585 100644 --- a/src/console/LibplanetConsole.Console.Executable/ClientRepositoryProcess.cs +++ b/src/console/LibplanetConsole.Console.Executable/ClientRepositoryProcess.cs @@ -6,7 +6,7 @@ internal sealed class ClientRepositoryProcess : ClientProcessBase { public required PrivateKey PrivateKey { get; init; } - public required EndPoint EndPoint { get; init; } + public required int Port { get; init; } public string OutputPath { get; set; } = string.Empty; @@ -16,7 +16,7 @@ internal sealed class ClientRepositoryProcess : ClientProcessBase OutputPath, "--private-key", PrivateKeyUtility.ToString(PrivateKey), - "--end-point", - EndPointUtility.ToString(EndPoint), + "--port", + $"{Port}", ]; } diff --git a/src/console/LibplanetConsole.Console.Executable/EntryCommands/InitializeCommand.cs b/src/console/LibplanetConsole.Console.Executable/EntryCommands/InitializeCommand.cs index a4676399..281c911a 100644 --- a/src/console/LibplanetConsole.Console.Executable/EntryCommands/InitializeCommand.cs +++ b/src/console/LibplanetConsole.Console.Executable/EntryCommands/InitializeCommand.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.ComponentModel.DataAnnotations; using JSSoft.Commands; using LibplanetConsole.Common; using LibplanetConsole.Common.DataAnnotations; @@ -6,12 +7,16 @@ using LibplanetConsole.Common.IO; using LibplanetConsole.DataAnnotations; using LibplanetConsole.Node; +using static LibplanetConsole.Common.EndPointUtility; namespace LibplanetConsole.Console.Executable.EntryCommands; [CommandSummary("Create a new repository to run Libplanet nodes and clients from the console.")] internal sealed class InitializeCommand : CommandBase { + private const int RandomPortSpacing = 5; + private readonly List _portList = []; + public InitializeCommand() : base("init") { @@ -19,15 +24,14 @@ public InitializeCommand() [CommandPropertyRequired] [CommandSummary("The directory path used to initialize a repository.")] - [Path( - Type = PathType.Directory, ExistsType = PathExistsType.NotExistOrEmpty)] + [Path(Type = PathType.Directory, ExistsType = PathExistsType.NotExistOrEmpty)] public string RepositoryPath { get; set; } = string.Empty; [CommandProperty] - [CommandSummary("The endpoint of the libplanet-console. " + - "If omitted, a random endpoint is used.")] - [EndPoint] - public string EndPoint { get; set; } = string.Empty; + [CommandSummary("The port of the libplanet-console. " + + "If omitted, a random port is used.")] + [NonNegative] + public int Port { get; set; } #if DEBUG [CommandProperty(InitValue = 1)] @@ -91,13 +95,27 @@ public InitializeCommand() [Category("Genesis")] public string ActionProviderType { get; set; } = string.Empty; + [CommandProperty("port-spacing", InitValue = 2)] + [CommandSummary("Specifies the spacing between ports. Default is 2. " + + "This option is only used when --port-generation-mode is set to " + + "'sequential'. If --port-generation-mode is set to 'random', " + + "the value of this option is 5'")] + [Category("Network")] + [Range(1, 10000)] + public int PortSpacing { get; set; } + + [CommandProperty("port-generation-mode")] + [CommandSummary("Specifies the mode for generating ports: Random or Sequential.")] + [Category("Network")] + public PortGenerationMode PortGenerationMode { get; set; } = PortGenerationMode.Sequential; + protected override void OnExecute() { var genesisKey = PrivateKeyUtility.ParseOrRandom(GenesisKey); - var endPoint = EndPointUtility.ParseOrNext(EndPoint); - var prevEndPoint = EndPoint != string.Empty ? endPoint : null; - var nodeOptions = GetNodeOptions(ref prevEndPoint); - var clientOptions = GetClientOptions(ref prevEndPoint); + var port = Port == 0 ? PortUtility.NextPort() : Port; + var nextPort = port; + var nodeOptions = GetNodeOptions(ref nextPort); + var clientOptions = GetClientOptions(ref nextPort); var outputPath = Path.GetFullPath(RepositoryPath); var dateTimeOffset = DateTimeOffset != DateTimeOffset.MinValue ? DateTimeOffset : DateTimeOffset.UtcNow; @@ -110,7 +128,7 @@ protected override void OnExecute() ActionProviderType = ActionProviderType, }; var genesis = Repository.CreateGenesis(genesisOptions); - var repository = new Repository(endPoint, nodeOptions, clientOptions) + var repository = new Repository(port, nodeOptions, clientOptions) { Genesis = genesis, LogPath = "log", @@ -134,17 +152,53 @@ protected override void OnExecute() TextWriterExtensions.WriteLineAsJson(writer, info); } - private NodeOptions[] GetNodeOptions(ref EndPoint? prevEndPoint) + private int NextPort(ref int nextPort) + { + nextPort = GetPort(nextPort); + _portList.Add(nextPort); + _portList.Sort(); + return nextPort; + + int GetPort(int nextPort) + { + if (PortGenerationMode == PortGenerationMode.Random) + { + var port = PortUtility.NextPort(); + while (IsValidRandomPort(port) is false) + { + port = PortUtility.NextPort(); + } + + return port; + } + + return nextPort + PortSpacing; + } + } + + private bool IsValidRandomPort(int randomPort) + { + for (var i = 0; i < _portList.Count; i++) + { + var port = _portList[i]; + if (Math.Abs(port - randomPort) < RandomPortSpacing) + { + return false; + } + } + + return true; + } + + private NodeOptions[] GetNodeOptions(ref int nextPort) { var privateKeys = GetNodes(); var nodeOptionsList = new List(privateKeys.Length); foreach (var privateKey in privateKeys) { - var endPoint = prevEndPoint ?? EndPointUtility.NextEndPoint(); - EndPointUtility.NextEndPoint(); var nodeOptions = new NodeOptions { - EndPoint = endPoint, + EndPoint = GetLocalHost(NextPort(ref nextPort)), PrivateKey = privateKey, StorePath = "store", LogPath = "log", @@ -152,33 +206,24 @@ private NodeOptions[] GetNodeOptions(ref EndPoint? prevEndPoint) ActionProviderType = ActionProviderType, }; nodeOptionsList.Add(nodeOptions); - if (prevEndPoint is not null) - { - prevEndPoint = endPoint; - } } return [.. nodeOptionsList]; } - private ClientOptions[] GetClientOptions(ref EndPoint? prevEndPoint) + private ClientOptions[] GetClientOptions(ref int nextPort) { var privateKeys = GetClients(); var clientOptionsList = new List(privateKeys.Length); foreach (var privateKey in privateKeys) { - var endPoint = prevEndPoint ?? EndPointUtility.NextEndPoint(); var clientOptions = new ClientOptions { - EndPoint = endPoint, + EndPoint = GetLocalHost(NextPort(ref nextPort)), PrivateKey = privateKey, LogPath = "log", }; clientOptionsList.Add(clientOptions); - if (prevEndPoint is not null) - { - prevEndPoint = endPoint; - } } return [.. clientOptionsList]; diff --git a/src/console/LibplanetConsole.Console.Executable/EntryCommands/PortGenerationMode.cs b/src/console/LibplanetConsole.Console.Executable/EntryCommands/PortGenerationMode.cs new file mode 100644 index 00000000..1a7dcf8a --- /dev/null +++ b/src/console/LibplanetConsole.Console.Executable/EntryCommands/PortGenerationMode.cs @@ -0,0 +1,8 @@ +namespace LibplanetConsole.Console.Executable.EntryCommands; + +public enum PortGenerationMode +{ + Random, + + Sequential, +} diff --git a/src/console/LibplanetConsole.Console.Executable/NodeRepositoryProcess.cs b/src/console/LibplanetConsole.Console.Executable/NodeRepositoryProcess.cs index 939a216e..374c144b 100644 --- a/src/console/LibplanetConsole.Console.Executable/NodeRepositoryProcess.cs +++ b/src/console/LibplanetConsole.Console.Executable/NodeRepositoryProcess.cs @@ -6,7 +6,7 @@ internal sealed class NodeRepositoryProcess : NodeProcessBase { public required PrivateKey PrivateKey { get; init; } - public required EndPoint EndPoint { get; init; } + public required int Port { get; init; } public string OutputPath { get; set; } = string.Empty; @@ -26,8 +26,8 @@ public override string[] Arguments OutputPath, "--private-key", PrivateKeyUtility.ToString(PrivateKey), - "--end-point", - EndPointUtility.ToString(EndPoint), + "--port", + $"{Port}", "--genesis-path", GenesisPath, }; diff --git a/src/console/LibplanetConsole.Console.Executable/Repository.cs b/src/console/LibplanetConsole.Console.Executable/Repository.cs index 82c6493b..fec0e324 100644 --- a/src/console/LibplanetConsole.Console.Executable/Repository.cs +++ b/src/console/LibplanetConsole.Console.Executable/Repository.cs @@ -5,6 +5,7 @@ using LibplanetConsole.Console.Extensions; using LibplanetConsole.Framework; using LibplanetConsole.Node; +using static LibplanetConsole.Common.EndPointUtility; namespace LibplanetConsole.Console.Executable; @@ -14,14 +15,20 @@ public sealed record class Repository private byte[]? _genesis; - public Repository(EndPoint endPoint, NodeOptions[] nodes, ClientOptions[] clients) + public Repository(int port, NodeOptions[] nodes, ClientOptions[] clients) { - EndPoint = endPoint; + if (port <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(port), port, "Port must be greater than 0."); + } + + Port = port; Nodes = nodes; Clients = clients; } - public EndPoint EndPoint { get; } + public int Port { get; } public NodeOptions[] Nodes { get; } = []; @@ -35,8 +42,6 @@ public byte[] Genesis public string LogPath { get; init; } = string.Empty; - public string Source { get; private set; } = string.Empty; - public static byte[] CreateGenesis(GenesisOptions genesisOptions) { var genesisProcess = new NodeGenesisProcess @@ -123,7 +128,7 @@ public dynamic Save(string repositoryPath, RepositoryPathResolver resolver) var clientsPath = resolver.GetClientsPath(repositoryPath); var applicationSettings = new ApplicationSettings { - EndPoint = EndPointUtility.ToString(EndPoint), + Port = Port, GenesisPath = PathUtility.GetRelativePath(settingsPath, genesisPath), LogPath = LogPath, }; @@ -146,7 +151,7 @@ public dynamic Save(string repositoryPath, RepositoryPathResolver resolver) var process = new NodeRepositoryProcess { PrivateKey = node.PrivateKey, - EndPoint = node.EndPoint, + Port = GetPort(node.EndPoint), OutputPath = nodePath, GenesisPath = genesisPath, ActionProviderModulePath = node.ActionProviderModulePath, @@ -166,7 +171,7 @@ public dynamic Save(string repositoryPath, RepositoryPathResolver resolver) var process = new ClientRepositoryProcess { PrivateKey = client.PrivateKey, - EndPoint = client.EndPoint, + Port = GetPort(client.EndPoint), OutputPath = clientPath, }; var sb = new StringBuilder(); diff --git a/src/console/LibplanetConsole.Console/ApplicationInfo.cs b/src/console/LibplanetConsole.Console/ApplicationInfo.cs index 62546ec7..8d2c2c2e 100644 --- a/src/console/LibplanetConsole.Console/ApplicationInfo.cs +++ b/src/console/LibplanetConsole.Console/ApplicationInfo.cs @@ -1,12 +1,8 @@ -using System.Text.Json.Serialization; -using LibplanetConsole.Common.Converters; - namespace LibplanetConsole.Console; public readonly record struct ApplicationInfo { - [JsonConverter(typeof(EndPointJsonConverter))] - public required EndPoint EndPoint { get; init; } + public required int Port { get; init; } public required string LogPath { get; init; } diff --git a/src/console/LibplanetConsole.Console/ApplicationInfoProvider.cs b/src/console/LibplanetConsole.Console/ApplicationInfoProvider.cs index 116b5aa0..63d52f42 100644 --- a/src/console/LibplanetConsole.Console/ApplicationInfoProvider.cs +++ b/src/console/LibplanetConsole.Console/ApplicationInfoProvider.cs @@ -12,7 +12,7 @@ public ApplicationInfoProvider(ApplicationOptions options) { _info = new() { - EndPoint = options.EndPoint, + Port = options.Port, LogPath = options.LogPath, NoProcess = options.NoProcess, Detach = options.Detach, diff --git a/src/console/LibplanetConsole.Console/ApplicationOptions.cs b/src/console/LibplanetConsole.Console/ApplicationOptions.cs index 06b91212..ca843d1f 100644 --- a/src/console/LibplanetConsole.Console/ApplicationOptions.cs +++ b/src/console/LibplanetConsole.Console/ApplicationOptions.cs @@ -2,12 +2,12 @@ namespace LibplanetConsole.Console; public sealed record class ApplicationOptions { - public ApplicationOptions(EndPoint endPoint) + public ApplicationOptions(int port) { - EndPoint = endPoint; + Port = port; } - public EndPoint EndPoint { get; } + public int Port { get; } public NodeOptions[] Nodes { get; init; } = []; diff --git a/src/console/LibplanetConsole.Console/ClientOptions.cs b/src/console/LibplanetConsole.Console/ClientOptions.cs index 8868a778..c9501917 100644 --- a/src/console/LibplanetConsole.Console/ClientOptions.cs +++ b/src/console/LibplanetConsole.Console/ClientOptions.cs @@ -1,6 +1,7 @@ using System.ComponentModel; using System.Text.Json.Serialization; using LibplanetConsole.Common; +using static LibplanetConsole.Common.EndPointUtility; namespace LibplanetConsole.Console; @@ -32,10 +33,10 @@ public static ClientOptions Load(string settingsPath) return new() { - EndPoint = EndPointUtility.Parse(applicationSettings.EndPoint), + EndPoint = GetLocalHost(applicationSettings.Port), PrivateKey = new PrivateKey(applicationSettings.PrivateKey), LogPath = Path.GetFullPath(applicationSettings.LogPath, repositoryPath), - NodeEndPoint = EndPointUtility.ParseOrDefault(applicationSettings.NodeEndPoint), + NodeEndPoint = ParseOrDefault(applicationSettings.NodeEndPoint), RepositoryPath = repositoryPath, }; } @@ -50,7 +51,7 @@ private sealed record class Settings private sealed record class ApplicationSettings { - public string EndPoint { get; init; } = string.Empty; + public int Port { get; init; } = 0; public string PrivateKey { get; init; } = string.Empty; diff --git a/src/console/LibplanetConsole.Console/ClientProcess.cs b/src/console/LibplanetConsole.Console/ClientProcess.cs index 8b20fe0a..6f74dcfc 100644 --- a/src/console/LibplanetConsole.Console/ClientProcess.cs +++ b/src/console/LibplanetConsole.Console/ClientProcess.cs @@ -1,4 +1,5 @@ using LibplanetConsole.Common; +using static LibplanetConsole.Common.EndPointUtility; using static LibplanetConsole.Console.ProcessEnvironment; namespace LibplanetConsole.Console; @@ -20,9 +21,14 @@ public override string[] Arguments } else { + if (GetHost(clientOptions.EndPoint) is not "localhost") + { + throw new InvalidOperationException("EndPoint must be localhost."); + } + argumentList.Add("run"); - argumentList.Add("--end-point"); - argumentList.Add(EndPointUtility.ToString(clientOptions.EndPoint)); + argumentList.Add("--port"); + argumentList.Add($"{GetPort(clientOptions.EndPoint)}"); argumentList.Add("--private-key"); argumentList.Add(PrivateKeyUtility.ToString(clientOptions.PrivateKey)); diff --git a/src/console/LibplanetConsole.Console/Node.cs b/src/console/LibplanetConsole.Console/Node.cs index 282d908d..690b1ee5 100644 --- a/src/console/LibplanetConsole.Console/Node.cs +++ b/src/console/LibplanetConsole.Console/Node.cs @@ -9,6 +9,7 @@ using LibplanetConsole.Node.Grpc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using static LibplanetConsole.Common.EndPointUtility; using NodeInfo = LibplanetConsole.Node.NodeInfo; namespace LibplanetConsole.Console; @@ -201,7 +202,7 @@ public async Task StartAsync(CancellationToken cancellationToken) var applicationOptions = this.GetRequiredService(); var seedEndPoint = EndPointUtility.ToString( - _nodeOptions.SeedEndPoint ?? applicationOptions.EndPoint); + _nodeOptions.SeedEndPoint ?? GetLocalHost(applicationOptions.Port)); var request = new StartRequest { SeedEndPoint = seedEndPoint, @@ -209,8 +210,8 @@ public async Task StartAsync(CancellationToken cancellationToken) var callOptions = new CallOptions(cancellationToken: cancellationToken); var response = await _nodeService.StartAsync(request, callOptions); _nodeInfo = response.NodeInfo; - _blocksyncEndPoint = EndPointUtility.Parse(_nodeInfo.SwarmEndPoint); - _consensusEndPoint = EndPointUtility.Parse(_nodeInfo.ConsensusEndPoint); + _blocksyncEndPoint = Parse(_nodeInfo.SwarmEndPoint); + _consensusEndPoint = Parse(_nodeInfo.ConsensusEndPoint); IsRunning = true; _logger.LogDebug("Node is started: {Address}", Address); await Task.WhenAll(Contents.Select(item => item.StartAsync(cancellationToken))); diff --git a/src/console/LibplanetConsole.Console/NodeOptions.cs b/src/console/LibplanetConsole.Console/NodeOptions.cs index 0ab15e2c..a96b842d 100644 --- a/src/console/LibplanetConsole.Console/NodeOptions.cs +++ b/src/console/LibplanetConsole.Console/NodeOptions.cs @@ -1,6 +1,7 @@ using System.ComponentModel; using System.Text.Json.Serialization; using LibplanetConsole.Common; +using static LibplanetConsole.Common.EndPointUtility; namespace LibplanetConsole.Console; @@ -38,11 +39,11 @@ public static NodeOptions Load(string settingsPath) return new() { - EndPoint = EndPointUtility.Parse(applicationSettings.EndPoint), + EndPoint = GetLocalHost(applicationSettings.Port), PrivateKey = new PrivateKey(applicationSettings.PrivateKey), StorePath = Path.GetFullPath(applicationSettings.StorePath, repositoryPath), LogPath = Path.GetFullPath(applicationSettings.LogPath, repositoryPath), - SeedEndPoint = EndPointUtility.ParseOrDefault(applicationSettings.SeedEndPoint), + SeedEndPoint = ParseOrDefault(applicationSettings.SeedEndPoint), RepositoryPath = repositoryPath, ActionProviderModulePath = applicationSettings.ActionProviderModulePath, ActionProviderType = applicationSettings.ActionProviderType, @@ -59,7 +60,7 @@ private sealed record class Settings private sealed record class ApplicationSettings { - public string EndPoint { get; init; } = string.Empty; + public int Port { get; init; } = 0; public string PrivateKey { get; init; } = string.Empty; diff --git a/src/console/LibplanetConsole.Console/NodeProcess.cs b/src/console/LibplanetConsole.Console/NodeProcess.cs index dd0a9ff0..efca7f2b 100644 --- a/src/console/LibplanetConsole.Console/NodeProcess.cs +++ b/src/console/LibplanetConsole.Console/NodeProcess.cs @@ -1,4 +1,5 @@ using LibplanetConsole.Common; +using static LibplanetConsole.Common.EndPointUtility; using static LibplanetConsole.Console.ProcessEnvironment; namespace LibplanetConsole.Console; @@ -19,9 +20,14 @@ public override string[] Arguments } else { + if (GetHost(nodeOptions.EndPoint) is not "localhost") + { + throw new InvalidOperationException("EndPoint must be localhost."); + } + argumentList.Add("run"); - argumentList.Add("--end-point"); - argumentList.Add(EndPointUtility.ToString(nodeOptions.EndPoint)); + argumentList.Add("--port"); + argumentList.Add($"{GetPort(nodeOptions.EndPoint)}"); argumentList.Add("--private-key"); argumentList.Add(PrivateKeyUtility.ToString(nodeOptions.PrivateKey)); diff --git a/src/console/LibplanetConsole.Console/SeedService.cs b/src/console/LibplanetConsole.Console/SeedService.cs index 88d2a7f8..d1943859 100644 --- a/src/console/LibplanetConsole.Console/SeedService.cs +++ b/src/console/LibplanetConsole.Console/SeedService.cs @@ -1,3 +1,4 @@ +using LibplanetConsole.Common; using LibplanetConsole.Seed; using Microsoft.Extensions.Hosting; using static LibplanetConsole.Common.EndPointUtility; @@ -34,12 +35,12 @@ public async Task StartAsync(CancellationToken cancellationToken) _blocksyncSeedNode = new SeedNode(new() { PrivateKey = _seedNodePrivateKey, - EndPoint = NextEndPoint(), + Port = PortUtility.NextPort(), }); _consensusSeedNode = new SeedNode(new() { PrivateKey = _seedNodePrivateKey, - EndPoint = NextEndPoint(), + Port = PortUtility.NextPort(), }); await _blocksyncSeedNode.StartAsync(cancellationToken); await _consensusSeedNode.StartAsync(cancellationToken); diff --git a/src/node/LibplanetConsole.Node.Executable/Application.cs b/src/node/LibplanetConsole.Node.Executable/Application.cs index bdb10619..788f3cd1 100644 --- a/src/node/LibplanetConsole.Node.Executable/Application.cs +++ b/src/node/LibplanetConsole.Node.Executable/Application.cs @@ -23,7 +23,7 @@ internal sealed class Application public Application(ApplicationOptions options, object[] instances) { - var (_, port) = EndPointUtility.GetHostAndPort(options.EndPoint); + var port = options.Port; var services = _builder.Services; foreach (var instance in instances) { @@ -33,6 +33,7 @@ public Application(ApplicationOptions options, object[] instances) _builder.WebHost.ConfigureKestrel(options => { options.ListenLocalhost(port, o => o.Protocols = HttpProtocols.Http2); + options.ListenLocalhost(port + 1, o => o.Protocols = HttpProtocols.Http1AndHttp2); }); if (options.LogPath != string.Empty) diff --git a/src/node/LibplanetConsole.Node.Executable/ApplicationSettings.cs b/src/node/LibplanetConsole.Node.Executable/ApplicationSettings.cs index 7557f2ea..31a40758 100644 --- a/src/node/LibplanetConsole.Node.Executable/ApplicationSettings.cs +++ b/src/node/LibplanetConsole.Node.Executable/ApplicationSettings.cs @@ -6,6 +6,7 @@ using LibplanetConsole.Common.DataAnnotations; using LibplanetConsole.DataAnnotations; using LibplanetConsole.Framework; +using static LibplanetConsole.Common.EndPointUtility; namespace LibplanetConsole.Node.Executable; @@ -13,10 +14,10 @@ namespace LibplanetConsole.Node.Executable; internal sealed record class ApplicationSettings { [CommandProperty] - [CommandSummary("Indicates the EndPoint on which the node will run. " + - "If omitted, a random endpoint is used.")] - [EndPoint] - public string EndPoint { get; init; } = string.Empty; + [CommandSummary("Indicates the port on which the node will run. " + + "If omitted, a random port is used.")] + [NonNegative] + public int Port { get; init; } [CommandProperty] [CommandSummary("Indicates the private key of the node. " + @@ -93,12 +94,12 @@ internal sealed record class ApplicationSettings public ApplicationOptions ToOptions() { - var endPoint = EndPointUtility.ParseOrNext(EndPoint); + var port = Port == 0 ? PortUtility.NextPort() : Port; var privateKey = PrivateKeyUtility.ParseOrRandom(PrivateKey); var genesis = TryGetGenesis(out var g) == true ? g : CreateGenesis(privateKey); var actionProvider = ModuleLoader.LoadActionLoader( ActionProviderModulePath, ActionProviderType); - return new ApplicationOptions(endPoint, privateKey, genesis) + return new ApplicationOptions(port, privateKey, genesis) { ParentProcessId = ParentProcessId, SeedEndPoint = GetSeedEndPoint(), @@ -115,10 +116,10 @@ static string GetFullPath(string path) { if (SeedEndPoint != string.Empty) { - return EndPointUtility.Parse(SeedEndPoint); + return Parse(SeedEndPoint); } - return IsSingleNode is true ? endPoint : null; + return IsSingleNode is true ? GetLocalHost(port) : null; } } diff --git a/src/node/LibplanetConsole.Node.Executable/EntryCommands/InitializeCommand.cs b/src/node/LibplanetConsole.Node.Executable/EntryCommands/InitializeCommand.cs index f1c1df52..702d20e3 100644 --- a/src/node/LibplanetConsole.Node.Executable/EntryCommands/InitializeCommand.cs +++ b/src/node/LibplanetConsole.Node.Executable/EntryCommands/InitializeCommand.cs @@ -5,6 +5,7 @@ using LibplanetConsole.Common.Extensions; using LibplanetConsole.Common.IO; using LibplanetConsole.DataAnnotations; +using static LibplanetConsole.Common.EndPointUtility; namespace LibplanetConsole.Node.Executable.EntryCommands; @@ -28,10 +29,10 @@ public InitializeCommand() public string PrivateKey { get; init; } = string.Empty; [CommandProperty] - [CommandSummary("The endpoint of the node. " + - "If omitted, a random endpoint is used.")] - [EndPoint] - public string EndPoint { get; set; } = string.Empty; + [CommandSummary("The port of the node. " + + "If omitted, a random port is used.")] + [NonNegative] + public int Port { get; set; } [CommandProperty] [CommandSummary("The directory path to store the block. " + @@ -90,19 +91,19 @@ public InitializeCommand() protected override void OnExecute() { var outputPath = Path.GetFullPath(RepositoryPath); - var endPoint = EndPointUtility.ParseOrNext(EndPoint); + var port = Port == 0 ? PortUtility.NextPort() : Port; var privateKey = PrivateKeyUtility.ParseOrRandom(PrivateKey); var storePath = Path.Combine(outputPath, StorePath.Fallback("store")); var logPath = Path.Combine(outputPath, LogPath.Fallback("log")); var genesisPath = Path.Combine(outputPath, GenesisPath.Fallback("genesis")); var repository = new Repository { - EndPoint = endPoint, + Port = port, PrivateKey = privateKey, StorePath = storePath, LogPath = logPath, GenesisPath = genesisPath, - SeedEndPoint = IsSingleNode is true ? endPoint : null, + SeedEndPoint = IsSingleNode is true ? GetLocalHost(port) : null, ActionProviderModulePath = ActionProviderModulePath, ActionProviderType = ActionProviderType, }; diff --git a/src/node/LibplanetConsole.Node.Executable/Repository.cs b/src/node/LibplanetConsole.Node.Executable/Repository.cs index c6a48c4f..38d52389 100644 --- a/src/node/LibplanetConsole.Node.Executable/Repository.cs +++ b/src/node/LibplanetConsole.Node.Executable/Repository.cs @@ -11,7 +11,7 @@ public sealed record class Repository public const string SettingsFileName = "node-settings.json"; public const string SettingsSchemaFileName = "node-settings-schema.json"; - public required EndPoint EndPoint { get; init; } + public required int Port { get; init; } public required PrivateKey PrivateKey { get; init; } @@ -62,7 +62,7 @@ public dynamic Save(string repositoryPath) Schema = SettingsSchemaFileName, Application = new ApplicationSettings { - EndPoint = EndPointUtility.ToString(EndPoint), + Port = Port, PrivateKey = PrivateKeyUtility.ToString(privateKey), GenesisPath = GetRelativePathFromDirectory(repositoryPath, GenesisPath), StorePath = GetRelativePathFromDirectory(repositoryPath, StorePath), diff --git a/src/node/LibplanetConsole.Node.Executable/Tracers/NodeEventTracer.cs b/src/node/LibplanetConsole.Node.Executable/Tracers/NodeEventTracer.cs index b76f6eae..328f9cbf 100644 --- a/src/node/LibplanetConsole.Node.Executable/Tracers/NodeEventTracer.cs +++ b/src/node/LibplanetConsole.Node.Executable/Tracers/NodeEventTracer.cs @@ -30,14 +30,14 @@ void IDisposable.Dispose() private void Node_Started(object? sender, EventArgs e) { - var endPoint = _options.EndPoint; + var endPoint = _options.Port; var message = $"Node has been started.: {endPoint}"; Console.Out.WriteColoredLine(message, TerminalColorType.BrightGreen); } private void Node_Stopped(object? sender, EventArgs e) { - var endPoint = _options.EndPoint; + var endPoint = _options.Port; var message = $"Node has been stopped.: {endPoint}"; Console.Out.WriteColoredLine(message, TerminalColorType.BrightGreen); } diff --git a/src/node/LibplanetConsole.Node/ApplicationInfo.cs b/src/node/LibplanetConsole.Node/ApplicationInfo.cs index 1ce3eb17..22e5f4be 100644 --- a/src/node/LibplanetConsole.Node/ApplicationInfo.cs +++ b/src/node/LibplanetConsole.Node/ApplicationInfo.cs @@ -5,8 +5,7 @@ namespace LibplanetConsole.Node; public readonly record struct ApplicationInfo { - [JsonConverter(typeof(EndPointJsonConverter))] - public required EndPoint EndPoint { get; init; } + public required int Port { get; init; } [JsonConverter(typeof(EndPointJsonConverter))] public required EndPoint? SeedEndPoint { get; init; } diff --git a/src/node/LibplanetConsole.Node/ApplicationInfoProvider.cs b/src/node/LibplanetConsole.Node/ApplicationInfoProvider.cs index 77c8e45b..1eaaf64e 100644 --- a/src/node/LibplanetConsole.Node/ApplicationInfoProvider.cs +++ b/src/node/LibplanetConsole.Node/ApplicationInfoProvider.cs @@ -12,7 +12,7 @@ public ApplicationInfoProvider(ApplicationOptions options) { _info = new() { - EndPoint = options.EndPoint, + Port = options.Port, SeedEndPoint = options.SeedEndPoint, StorePath = options.StorePath, LogPath = options.LogPath, diff --git a/src/node/LibplanetConsole.Node/ApplicationOptions.cs b/src/node/LibplanetConsole.Node/ApplicationOptions.cs index 9115635b..bcf1bb99 100644 --- a/src/node/LibplanetConsole.Node/ApplicationOptions.cs +++ b/src/node/LibplanetConsole.Node/ApplicationOptions.cs @@ -2,14 +2,14 @@ namespace LibplanetConsole.Node; public sealed record class ApplicationOptions { - public ApplicationOptions(EndPoint endPoint, PrivateKey privateKey, byte[] genesis) + public ApplicationOptions(int port, PrivateKey privateKey, byte[] genesis) { - EndPoint = endPoint; + Port = port; PrivateKey = privateKey; Genesis = genesis; } - public EndPoint EndPoint { get; } + public int Port { get; } public PrivateKey PrivateKey { get; } diff --git a/src/node/LibplanetConsole.Node/SeedService.cs b/src/node/LibplanetConsole.Node/SeedService.cs index ea8f1600..b72cfd2a 100644 --- a/src/node/LibplanetConsole.Node/SeedService.cs +++ b/src/node/LibplanetConsole.Node/SeedService.cs @@ -1,3 +1,4 @@ +using LibplanetConsole.Common; using LibplanetConsole.Seed; using static LibplanetConsole.Common.EndPointUtility; @@ -35,12 +36,12 @@ public async Task StartAsync(CancellationToken cancellationToken) var blocksyncSeedNode = new SeedNode(new() { PrivateKey = _seedNodePrivateKey, - EndPoint = NextEndPoint(), + Port = PortUtility.NextPort(), }); var consensusSeedNode = new SeedNode(new() { PrivateKey = _seedNodePrivateKey, - EndPoint = NextEndPoint(), + Port = PortUtility.NextPort(), }); await blocksyncSeedNode.StartAsync(cancellationToken); await consensusSeedNode.StartAsync(cancellationToken); diff --git a/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs b/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs index cc2d16e2..bb47fcdc 100644 --- a/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs +++ b/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs @@ -14,10 +14,11 @@ public static IServiceCollection AddNode( this IServiceCollection @this, ApplicationOptions options) { var synchronizationContext = SynchronizationContext.Current ?? new(); + var localHost = GetLocalHost(options.Port); SynchronizationContext.SetSynchronizationContext(synchronizationContext); @this.AddSingleton(synchronizationContext); @this.AddSingleton(options); - if (CompareEndPoint(options.SeedEndPoint, options.EndPoint) is true) + if (CompareEndPoint(options.SeedEndPoint, localHost) is true) { @this.AddSingleton() .AddSingleton(s => s.GetRequiredService());