From c1dc68b366d4e5e805e9064bffcfd434859e739a Mon Sep 17 00:00:00 2001 From: s2quake Date: Thu, 3 Oct 2024 22:11:38 +0900 Subject: [PATCH 01/18] refactor: Remove communication package --- Directory.Build.props | 1 - .../Example.cs | 18 ++- .../ExampleRemoteService.cs | 26 ++-- .../ExampleService.cs | 18 +-- .../ServiceCollectionExtensions.cs | 5 +- .../ApplicationBase.cs | 3 +- .../Client.BlockChain.cs | 48 ++++--- src/client/LibplanetConsole.Client/Client.cs | 71 +++++----- .../ServiceCollectionExtensions.cs | 21 ++- .../Services/ClientService.cs | 98 +++++++------- .../Services/ClientServiceContext.cs | 12 +- .../Services/RemoteBlockChainService.cs | 14 +- .../Services/RemoteNodeContext.cs | 14 +- .../Services/RemoteNodeService.cs | 14 +- .../EndPointUtility.cs | 28 +++- .../LibplanetConsole.Common/InfoUtility.cs | 2 +- .../LibplanetConsole.Common.csproj | 21 +-- .../Services/ILocalService.cs | 12 +- .../Services/IRemoteService.cs | 12 +- .../Services/LocalService.cs | 124 +++++++++--------- .../Services/LocalServiceContext.cs | 114 ++++++++-------- .../Services/RemoteService.cs | 100 +++++++------- .../Services/RemoteServiceContext.cs | 104 +++++++-------- .../Evidence.cs | 26 ++-- .../ExampleClient.cs | 15 ++- .../ExampleNode.cs | 40 ++++-- .../ApplicationSettings.cs | 1 - .../Explorer.cs | 21 ++- .../ServiceCollectionExtensions.cs | 5 +- .../ApplicationBase.cs | 2 +- .../LibplanetConsole.Console/Client.cs | 117 ++++++++--------- .../ConsoleServiceContext.cs | 12 +- .../Node.BlockChain.cs | 92 +++++++------ src/console/LibplanetConsole.Console/Node.cs | 111 ++++++++-------- .../NodeCollection.cs | 3 +- .../ServiceCollectionExtensions.cs | 10 +- .../Services/IClientContentService.cs | 12 +- .../Services/INodeContentService.cs | 12 +- .../Services/SeedService.cs | 110 ++++++++-------- .../ServiceCollectionExtensions.cs | 4 +- .../Services/EvidenceService.cs | 47 ++++--- .../ExampleService.cs | 65 +++++---- .../ServiceCollectionExtensions.cs | 3 +- .../ServiceCollectionExtensions.cs | 4 +- .../Services/ExplorerService.cs | 38 +++--- .../LibplanetConsole.Node/ApplicationBase.cs | 2 +- .../LibplanetConsole.Node.csproj | 3 + src/node/LibplanetConsole.Node/Node.cs | 58 ++++---- .../ServiceCollectionExtensions.cs | 13 +- .../Services/BlockChainService.cs | 112 ++++++++-------- .../Services/NodeContext.cs | 12 +- .../Services/NodeService.cs | 26 ++-- .../Services/SeedService.cs | 119 +++++++++-------- .../Services/NodeService.proto | 44 +++++++ 54 files changed, 1048 insertions(+), 971 deletions(-) create mode 100644 src/shared/LibplanetConsole.Node/Services/NodeService.proto diff --git a/Directory.Build.props b/Directory.Build.props index c7dc61ab..be5eacf5 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,7 +5,6 @@ enable enable $(MSBuildThisFileDirectory).submodules\commands\ - $(MSBuildThisFileDirectory).submodules\communication\ $(MSBuildThisFileDirectory).submodules\libplanet\ $(NoWarn);NU1902;NU1903 diff --git a/src/client/LibplanetConsole.Client.Example/Example.cs b/src/client/LibplanetConsole.Client.Example/Example.cs index e76483b2..eba4f624 100644 --- a/src/client/LibplanetConsole.Client.Example/Example.cs +++ b/src/client/LibplanetConsole.Client.Example/Example.cs @@ -1,22 +1,26 @@ -using LibplanetConsole.Example.Services; - namespace LibplanetConsole.Client.Example; internal sealed class Example( IClient client, - ExampleRemoteService remoteNodeService, + // ExampleRemoteService remoteNodeService, ExampleSettings settings) : IExample { - private readonly ExampleRemoteService _remoteNodeService = remoteNodeService; + // private readonly ExampleRemoteService _remoteNodeService = remoteNodeService; public Address Address => client.Address; public bool IsExample { get; } = settings.IsExample; - private IExampleNodeService Server => _remoteNodeService.Service; + // private IExampleNodeService Server => _remoteNodeService.Service; - public void Subscribe() => Server.Subscribe(Address); + public void Subscribe() + { + // Server.Subscribe(Address); + } - public void Unsubscribe() => Server.Unsubscribe(Address); + public void Unsubscribe() + { + // Server.Unsubscribe(Address); + } } diff --git a/src/client/LibplanetConsole.Client.Example/ExampleRemoteService.cs b/src/client/LibplanetConsole.Client.Example/ExampleRemoteService.cs index df0f355b..868e8dc7 100644 --- a/src/client/LibplanetConsole.Client.Example/ExampleRemoteService.cs +++ b/src/client/LibplanetConsole.Client.Example/ExampleRemoteService.cs @@ -1,16 +1,16 @@ -using LibplanetConsole.Common.Services; -using LibplanetConsole.Example.Services; +// using LibplanetConsole.Common.Services; +// using LibplanetConsole.Example.Services; -namespace LibplanetConsole.Client.Example; +// namespace LibplanetConsole.Client.Example; -internal sealed class ExampleRemoteService - : RemoteService, IExampleNodeCallback -{ - public void OnSubscribed(Address address) - { - } +// internal sealed class ExampleRemoteService +// : RemoteService, IExampleNodeCallback +// { +// public void OnSubscribed(Address address) +// { +// } - public void OnUnsubscribed(Address address) - { - } -} +// public void OnUnsubscribed(Address address) +// { +// } +// } diff --git a/src/client/LibplanetConsole.Client.Example/ExampleService.cs b/src/client/LibplanetConsole.Client.Example/ExampleService.cs index 1f9dcca2..43233e81 100644 --- a/src/client/LibplanetConsole.Client.Example/ExampleService.cs +++ b/src/client/LibplanetConsole.Client.Example/ExampleService.cs @@ -1,12 +1,12 @@ -using LibplanetConsole.Common.Services; -using LibplanetConsole.Example.Services; +// using LibplanetConsole.Common.Services; +// using LibplanetConsole.Example.Services; -namespace LibplanetConsole.Client.Example; +// namespace LibplanetConsole.Client.Example; -internal sealed class ExampleService(IExample sampleClient) - : LocalService, IExampleClientService -{ - public void Subscribe() => sampleClient.Subscribe(); +// internal sealed class ExampleService(IExample sampleClient) +// : LocalService, IExampleClientService +// { +// public void Subscribe() => sampleClient.Subscribe(); - public void Unsubscribe() => sampleClient.Unsubscribe(); -} +// public void Unsubscribe() => sampleClient.Unsubscribe(); +// } diff --git a/src/client/LibplanetConsole.Client.Example/ServiceCollectionExtensions.cs b/src/client/LibplanetConsole.Client.Example/ServiceCollectionExtensions.cs index 74a716f5..b261ab6c 100644 --- a/src/client/LibplanetConsole.Client.Example/ServiceCollectionExtensions.cs +++ b/src/client/LibplanetConsole.Client.Example/ServiceCollectionExtensions.cs @@ -1,6 +1,5 @@ using JSSoft.Commands; using LibplanetConsole.Common; -using LibplanetConsole.Common.Services; using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Client.Example; @@ -13,8 +12,8 @@ public static IServiceCollection AddExample(this IServiceCollection @this) .AddSingleton(s => s.GetRequiredService()); @this.AddSingleton(); @this.AddSingleton(); - @this.AddSingleton(); - @this.AddSingleton(); + // @this.AddSingleton(); + // @this.AddSingleton(); return @this; } } diff --git a/src/client/LibplanetConsole.Client/ApplicationBase.cs b/src/client/LibplanetConsole.Client/ApplicationBase.cs index 647df113..99d57d77 100644 --- a/src/client/LibplanetConsole.Client/ApplicationBase.cs +++ b/src/client/LibplanetConsole.Client/ApplicationBase.cs @@ -15,7 +15,7 @@ public abstract class ApplicationBase : ApplicationFramework, IApplication private readonly ApplicationInfo _info; private readonly ILogger _logger; private readonly Task _waitForExitTask = Task.CompletedTask; - private ClientServiceContext? _clientServiceContext; + // private ClientServiceContext? _clientServiceContext; private Guid _closeToken; protected ApplicationBase(IServiceProvider serviceProvider, ApplicationOptions options) @@ -26,7 +26,6 @@ protected ApplicationBase(IServiceProvider serviceProvider, ApplicationOptions o _logger.LogDebug(Environment.CommandLine); _logger.LogDebug("Application initializing..."); _client = serviceProvider.GetRequiredService(); - _info = new() { EndPoint = options.EndPoint, diff --git a/src/client/LibplanetConsole.Client/Client.BlockChain.cs b/src/client/LibplanetConsole.Client/Client.BlockChain.cs index 9a5e94d4..40aaaad6 100644 --- a/src/client/LibplanetConsole.Client/Client.BlockChain.cs +++ b/src/client/LibplanetConsole.Client/Client.BlockChain.cs @@ -27,13 +27,22 @@ public async Task SendTransactionAsync( } public Task GetBlockHashAsync(long height, CancellationToken cancellationToken) - => RemoteBlockChainService.GetBlockHashAsync(height, cancellationToken); + { + // => RemoteBlockChainService.GetBlockHashAsync(height, cancellationToken); + throw new NotImplementedException(); + } public Task GetNextNonceAsync(Address address, CancellationToken cancellationToken) - => RemoteBlockChainService.GetNextNonceAsync(address, cancellationToken); + { + // return RemoteBlockChainService.GetNextNonceAsync(address, cancellationToken); + throw new NotImplementedException(); + } public Task GetTipHashAsync(CancellationToken cancellationToken) - => RemoteBlockChainService.GetTipHashAsync(cancellationToken); + { + // return RemoteBlockChainService.GetTipHashAsync(cancellationToken); + throw new NotImplementedException(); + } public async Task GetStateAsync( BlockHash? blockHash, @@ -41,9 +50,10 @@ public async Task GetStateAsync( Address address, CancellationToken cancellationToken) { - var value = await RemoteBlockChainService.GetStateAsync( - blockHash, accountAddress, address, cancellationToken); - return _codec.Decode(value); + // var value = await RemoteBlockChainService.GetStateAsync( + // blockHash, accountAddress, address, cancellationToken); + // return _codec.Decode(value); + throw new NotImplementedException(); } public async Task GetStateByStateRootHashAsync( @@ -52,24 +62,26 @@ public async Task GetStateByStateRootHashAsync( Address address, CancellationToken cancellationToken) { - var value = await RemoteBlockChainService.GetStateByStateRootHashAsync( - stateRootHash, accountAddress, address, cancellationToken); - return _codec.Decode(value); + // var value = await RemoteBlockChainService.GetStateByStateRootHashAsync( + // stateRootHash, accountAddress, address, cancellationToken); + // return _codec.Decode(value); + throw new NotImplementedException(); } public async Task GetActionAsync( TxId txId, int actionIndex, CancellationToken cancellationToken) where T : IAction { - var bytes = await RemoteBlockChainService.GetActionAsync( - txId, actionIndex, cancellationToken); - var value = _codec.Decode(bytes); - if (Activator.CreateInstance(typeof(T)) is T action) - { - action.LoadPlainValue(value); - return action; - } + // var bytes = await RemoteBlockChainService.GetActionAsync( + // txId, actionIndex, cancellationToken); + // var value = _codec.Decode(bytes); + // if (Activator.CreateInstance(typeof(T)) is T action) + // { + // action.LoadPlainValue(value); + // return action; + // } - throw new InvalidOperationException("Action not found."); + // throw new InvalidOperationException("Action not found."); + throw new NotImplementedException(); } } diff --git a/src/client/LibplanetConsole.Client/Client.cs b/src/client/LibplanetConsole.Client/Client.cs index ca7dd548..5b8a3d01 100644 --- a/src/client/LibplanetConsole.Client/Client.cs +++ b/src/client/LibplanetConsole.Client/Client.cs @@ -1,5 +1,4 @@ using System.Security; -using LibplanetConsole.Client.Services; using LibplanetConsole.Common; using LibplanetConsole.Common.Extensions; using LibplanetConsole.Node; @@ -15,7 +14,7 @@ internal sealed partial class Client : IClient, INodeCallback, IBlockChainCallba private readonly SecureString _privateKey; private readonly ILogger _logger; private EndPoint? _nodeEndPoint; - private RemoteNodeContext? _remoteNodeContext; + // private RemoteNodeContext? _remoteNodeContext; private Guid _closeToken; private ClientInfo _info; @@ -64,11 +63,11 @@ public EndPoint NodeEndPoint public bool IsRunning { get; private set; } - private INodeService RemoteNodeService - => _serviceProvider.GetRequiredService().Service; + // private INodeService RemoteNodeService + // => _serviceProvider.GetRequiredService().Service; - private IBlockChainService RemoteBlockChainService - => _serviceProvider.GetRequiredService().Service; + // private IBlockChainService RemoteBlockChainService + // => _serviceProvider.GetRequiredService().Service; public override string ToString() => $"[{Address}]"; @@ -76,16 +75,16 @@ private IBlockChainService RemoteBlockChainService public async Task StartAsync(CancellationToken cancellationToken) { - if (_remoteNodeContext is not null) - { - throw new InvalidOperationException("The client is already running."); - } - - _remoteNodeContext = _serviceProvider.GetRequiredService(); - _remoteNodeContext.EndPoint = NodeEndPoint; - _closeToken = await _remoteNodeContext.OpenAsync(cancellationToken); - _remoteNodeContext.Closed += RemoteNodeContext_Closed; - NodeInfo = await RemoteNodeService.GetInfoAsync(cancellationToken); + // if (_remoteNodeContext is not null) + // { + // throw new InvalidOperationException("The client is already running."); + // } + + // _remoteNodeContext = _serviceProvider.GetRequiredService(); + // _remoteNodeContext.EndPoint = NodeEndPoint; + // _closeToken = await _remoteNodeContext.OpenAsync(cancellationToken); + // _remoteNodeContext.Closed += RemoteNodeContext_Closed; + // NodeInfo = await RemoteNodeService.GetInfoAsync(cancellationToken); _info = _info with { NodeAddress = NodeInfo.Address }; IsRunning = true; _logger.LogDebug( @@ -95,15 +94,15 @@ public async Task StartAsync(CancellationToken cancellationToken) public async Task StopAsync(CancellationToken cancellationToken) { - if (_remoteNodeContext is null) - { - throw new InvalidOperationException("The client is not running."); - } - - _remoteNodeContext.Closed -= RemoteNodeContext_Closed; - await _remoteNodeContext.CloseAsync(_closeToken, cancellationToken); - _info = _info with { NodeAddress = default }; - _remoteNodeContext = null; + // if (_remoteNodeContext is null) + // { + // throw new InvalidOperationException("The client is not running."); + // } + + // _remoteNodeContext.Closed -= RemoteNodeContext_Closed; + // await _remoteNodeContext.CloseAsync(_closeToken, cancellationToken); + // _info = _info with { NodeAddress = default }; + // _remoteNodeContext = null; _closeToken = Guid.Empty; IsRunning = false; _logger.LogDebug("Client is stopped: {Address}", Address); @@ -127,12 +126,12 @@ public void InvokeBlockAppendedEvent(BlockInfo blockInfo) public async ValueTask DisposeAsync() { - if (_remoteNodeContext is not null) - { - _remoteNodeContext.Closed -= RemoteNodeContext_Closed; - await _remoteNodeContext.CloseAsync(_closeToken); - _remoteNodeContext = null; - } + // if (_remoteNodeContext is not null) + // { + // _remoteNodeContext.Closed -= RemoteNodeContext_Closed; + // await _remoteNodeContext.CloseAsync(_closeToken); + // _remoteNodeContext = null; + // } } void INodeCallback.OnStarted(NodeInfo nodeInfo) => NodeInfo = nodeInfo; @@ -146,11 +145,11 @@ void IBlockChainCallback.OnBlockAppended(BlockInfo blockInfo) private void RemoteNodeContext_Closed(object? sender, EventArgs e) { - if (_remoteNodeContext is not null) - { - _remoteNodeContext.Closed -= RemoteNodeContext_Closed; - _remoteNodeContext = null; - } + // if (_remoteNodeContext is not null) + // { + // _remoteNodeContext.Closed -= RemoteNodeContext_Closed; + // _remoteNodeContext = null; + // } _closeToken = Guid.Empty; IsRunning = false; diff --git a/src/client/LibplanetConsole.Client/ServiceCollectionExtensions.cs b/src/client/LibplanetConsole.Client/ServiceCollectionExtensions.cs index d153241a..bf063269 100644 --- a/src/client/LibplanetConsole.Client/ServiceCollectionExtensions.cs +++ b/src/client/LibplanetConsole.Client/ServiceCollectionExtensions.cs @@ -1,7 +1,6 @@ using LibplanetConsole.Client.Commands; -using LibplanetConsole.Client.Services; using LibplanetConsole.Common; -using LibplanetConsole.Common.Services; +// using LibplanetConsole.Common.Services; using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Client; @@ -15,15 +14,15 @@ public static IServiceCollection AddClient( .AddSingleton(s => s.GetRequiredService()) .AddSingleton(s => s.GetRequiredService()); - @this.AddSingleton() - .AddSingleton(s => s.GetRequiredService()) - .AddSingleton(s => s.GetRequiredService()); - @this.AddSingleton(); - @this.AddSingleton() - .AddSingleton(s => s.GetRequiredService()); - @this.AddSingleton(); - @this.AddSingleton() - .AddSingleton(s => s.GetRequiredService()); + // @this.AddSingleton() + // .AddSingleton(s => s.GetRequiredService()) + // .AddSingleton(s => s.GetRequiredService()); + // @this.AddSingleton(); + // @this.AddSingleton(); + // .AddSingleton(s => s.GetRequiredService()); + // @this.AddSingleton(); + // @this.AddSingleton(); + // .AddSingleton(s => s.GetRequiredService()); @this.AddSingleton(); @this.AddSingleton(); diff --git a/src/client/LibplanetConsole.Client/Services/ClientService.cs b/src/client/LibplanetConsole.Client/Services/ClientService.cs index 52e66859..431fe863 100644 --- a/src/client/LibplanetConsole.Client/Services/ClientService.cs +++ b/src/client/LibplanetConsole.Client/Services/ClientService.cs @@ -1,49 +1,49 @@ -using LibplanetConsole.Common; -using LibplanetConsole.Common.Actions; -using LibplanetConsole.Common.Services; - -namespace LibplanetConsole.Client.Services; - -internal sealed class ClientService : LocalService, IClientService -{ - private readonly Client _client; - - public ClientService(Client client) - { - _client = client; - _client.Started += (s, e) => Callback.OnStarted(_client.Info); - _client.Stopped += (s, e) => Callback.OnStopped(); - } - - public async Task GetInfoAsync(CancellationToken cancellationToken) - { - await Task.CompletedTask; - return _client.Info; - } - - public async Task StartAsync( - string nodeEndPoint, CancellationToken cancellationToken) - { - _client.NodeEndPoint = EndPointUtility.Parse(nodeEndPoint); - await _client.StartAsync(cancellationToken); - return _client.Info; - } - - public Task StopAsync(CancellationToken cancellationToken) - => _client.StopAsync(cancellationToken); - - public async Task SendTransactionAsync( - TransactionOptions transactionOptions, CancellationToken cancellationToken) - { - if (transactionOptions.TryVerify(_client) == true) - { - var action = new StringAction - { - Value = transactionOptions.Text, - }; - return await _client.SendTransactionAsync([action], cancellationToken); - } - - throw new InvalidOperationException("The signature is invalid."); - } -} +// using LibplanetConsole.Common; +// using LibplanetConsole.Common.Actions; +// using LibplanetConsole.Common.Services; + +// namespace LibplanetConsole.Client.Services; + +// internal sealed class ClientService : LocalService, IClientService +// { +// private readonly Client _client; + +// public ClientService(Client client) +// { +// _client = client; +// _client.Started += (s, e) => Callback.OnStarted(_client.Info); +// _client.Stopped += (s, e) => Callback.OnStopped(); +// } + +// public async Task GetInfoAsync(CancellationToken cancellationToken) +// { +// await Task.CompletedTask; +// return _client.Info; +// } + +// public async Task StartAsync( +// string nodeEndPoint, CancellationToken cancellationToken) +// { +// _client.NodeEndPoint = EndPointUtility.Parse(nodeEndPoint); +// await _client.StartAsync(cancellationToken); +// return _client.Info; +// } + +// public Task StopAsync(CancellationToken cancellationToken) +// => _client.StopAsync(cancellationToken); + +// public async Task SendTransactionAsync( +// TransactionOptions transactionOptions, CancellationToken cancellationToken) +// { +// if (transactionOptions.TryVerify(_client) == true) +// { +// var action = new StringAction +// { +// Value = transactionOptions.Text, +// }; +// return await _client.SendTransactionAsync([action], cancellationToken); +// } + +// throw new InvalidOperationException("The signature is invalid."); +// } +// } diff --git a/src/client/LibplanetConsole.Client/Services/ClientServiceContext.cs b/src/client/LibplanetConsole.Client/Services/ClientServiceContext.cs index 58f35369..94f99a07 100644 --- a/src/client/LibplanetConsole.Client/Services/ClientServiceContext.cs +++ b/src/client/LibplanetConsole.Client/Services/ClientServiceContext.cs @@ -1,8 +1,8 @@ -using LibplanetConsole.Common.Services; +// using LibplanetConsole.Common.Services; -namespace LibplanetConsole.Client.Services; +// namespace LibplanetConsole.Client.Services; -internal sealed class ClientServiceContext( - IEnumerable localServices) : LocalServiceContext([.. localServices]) -{ -} +// internal sealed class ClientServiceContext( +// IEnumerable localServices) : LocalServiceContext([.. localServices]) +// { +// } diff --git a/src/client/LibplanetConsole.Client/Services/RemoteBlockChainService.cs b/src/client/LibplanetConsole.Client/Services/RemoteBlockChainService.cs index 209d6161..68a2a40d 100644 --- a/src/client/LibplanetConsole.Client/Services/RemoteBlockChainService.cs +++ b/src/client/LibplanetConsole.Client/Services/RemoteBlockChainService.cs @@ -1,9 +1,9 @@ -using LibplanetConsole.Common.Services; -using LibplanetConsole.Node.Services; +// using LibplanetConsole.Common.Services; +// using LibplanetConsole.Node.Services; -namespace LibplanetConsole.Client.Services; +// namespace LibplanetConsole.Client.Services; -internal sealed class RemoteBlockChainService(Client client) - : RemoteService(client) -{ -} +// internal sealed class RemoteBlockChainService(Client client) +// : RemoteService(client) +// { +// } diff --git a/src/client/LibplanetConsole.Client/Services/RemoteNodeContext.cs b/src/client/LibplanetConsole.Client/Services/RemoteNodeContext.cs index 4bb5d0d6..44beebd3 100644 --- a/src/client/LibplanetConsole.Client/Services/RemoteNodeContext.cs +++ b/src/client/LibplanetConsole.Client/Services/RemoteNodeContext.cs @@ -1,9 +1,9 @@ -using LibplanetConsole.Common.Services; +// using LibplanetConsole.Common.Services; -namespace LibplanetConsole.Client.Services; +// namespace LibplanetConsole.Client.Services; -internal sealed class RemoteNodeContext( - IEnumerable remoteServices) - : RemoteServiceContext([.. remoteServices]) -{ -} +// internal sealed class RemoteNodeContext( +// IEnumerable remoteServices) +// : RemoteServiceContext([.. remoteServices]) +// { +// } diff --git a/src/client/LibplanetConsole.Client/Services/RemoteNodeService.cs b/src/client/LibplanetConsole.Client/Services/RemoteNodeService.cs index bb9deb78..49a61a86 100644 --- a/src/client/LibplanetConsole.Client/Services/RemoteNodeService.cs +++ b/src/client/LibplanetConsole.Client/Services/RemoteNodeService.cs @@ -1,9 +1,9 @@ -using LibplanetConsole.Common.Services; -using LibplanetConsole.Node.Services; +// using LibplanetConsole.Common.Services; +// using LibplanetConsole.Node.Services; -namespace LibplanetConsole.Client.Services; +// namespace LibplanetConsole.Client.Services; -internal sealed class RemoteNodeService(Client client) - : RemoteService(client) -{ -} +// internal sealed class RemoteNodeService(Client client) +// : RemoteService(client) +// { +// } diff --git a/src/common/LibplanetConsole.Common/EndPointUtility.cs b/src/common/LibplanetConsole.Common/EndPointUtility.cs index cc161f41..2c5c46ca 100644 --- a/src/common/LibplanetConsole.Common/EndPointUtility.cs +++ b/src/common/LibplanetConsole.Common/EndPointUtility.cs @@ -1,6 +1,5 @@ using System.Diagnostics.CodeAnalysis; using System.Net.Sockets; -using CommunicationUtility = JSSoft.Communication.EndPointUtility; namespace LibplanetConsole.Common; @@ -11,7 +10,20 @@ public static class EndPointUtility public static EndPoint NextEndPoint() => new DnsEndPoint("localhost", GetPort()); - public static EndPoint Parse(string text) => CommunicationUtility.Parse(text); + public static EndPoint Parse(string text) + { + var items = text.Split(':'); + if (IPAddress.TryParse(items[0], out var address) == true) + { + return new IPEndPoint(address, int.Parse(items[1])); + } + else if (items.Length == 2) + { + return new DnsEndPoint(items[0], int.Parse(items[1])); + } + + throw new NotSupportedException($"'{text}' is not supported."); + } public static EndPoint ParseOrNext(string text) => text == string.Empty ? NextEndPoint() : Parse(text); @@ -24,14 +36,16 @@ public static EndPoint ParseOrFallback(string text, EndPoint fallback) public static bool TryParse(string text, [MaybeNullWhen(false)] out EndPoint endPoint) { - if (CommunicationUtility.TryParse(text, out var value)) + try { - endPoint = value; + endPoint = Parse(text); return true; } - - endPoint = null; - return false; + catch + { + endPoint = null; + return false; + } } public static (string Host, int Port) GetHostAndPort(EndPoint endPoint) diff --git a/src/common/LibplanetConsole.Common/InfoUtility.cs b/src/common/LibplanetConsole.Common/InfoUtility.cs index f8e469a0..5da11df7 100644 --- a/src/common/LibplanetConsole.Common/InfoUtility.cs +++ b/src/common/LibplanetConsole.Common/InfoUtility.cs @@ -27,7 +27,7 @@ orderby name public static ImmutableDictionary GetInfo( IServiceProvider serviceProvider, object obj) { - var infoProviders = serviceProvider.GetRequiredService>(); + var infoProviders = serviceProvider.GetServices(); return GetInfo(infoProviders, obj); } diff --git a/src/common/LibplanetConsole.Common/LibplanetConsole.Common.csproj b/src/common/LibplanetConsole.Common/LibplanetConsole.Common.csproj index 998bbf95..0feff169 100644 --- a/src/common/LibplanetConsole.Common/LibplanetConsole.Common.csproj +++ b/src/common/LibplanetConsole.Common/LibplanetConsole.Common.csproj @@ -31,30 +31,13 @@ - - - - - - - - - - - - - - - - - - - + + diff --git a/src/common/LibplanetConsole.Common/Services/ILocalService.cs b/src/common/LibplanetConsole.Common/Services/ILocalService.cs index 7cef397f..44c589d5 100644 --- a/src/common/LibplanetConsole.Common/Services/ILocalService.cs +++ b/src/common/LibplanetConsole.Common/Services/ILocalService.cs @@ -1,8 +1,8 @@ -using JSSoft.Communication; +// using JSSoft.Communication; -namespace LibplanetConsole.Common.Services; +// namespace LibplanetConsole.Common.Services; -public interface ILocalService -{ - IService Service { get; } -} +// public interface ILocalService +// { +// IService Service { get; } +// } diff --git a/src/common/LibplanetConsole.Common/Services/IRemoteService.cs b/src/common/LibplanetConsole.Common/Services/IRemoteService.cs index 2b812a87..d1d6f63e 100644 --- a/src/common/LibplanetConsole.Common/Services/IRemoteService.cs +++ b/src/common/LibplanetConsole.Common/Services/IRemoteService.cs @@ -1,8 +1,8 @@ -using JSSoft.Communication; +// using JSSoft.Communication; -namespace LibplanetConsole.Common.Services; +// namespace LibplanetConsole.Common.Services; -public interface IRemoteService -{ - IService Service { get; } -} +// public interface IRemoteService +// { +// IService Service { get; } +// } diff --git a/src/common/LibplanetConsole.Common/Services/LocalService.cs b/src/common/LibplanetConsole.Common/Services/LocalService.cs index eb408423..c3b25a53 100644 --- a/src/common/LibplanetConsole.Common/Services/LocalService.cs +++ b/src/common/LibplanetConsole.Common/Services/LocalService.cs @@ -1,62 +1,62 @@ -// File may only contain a single type -#pragma warning disable SA1402 -using JSSoft.Communication; - -namespace LibplanetConsole.Common.Services; - -public class LocalService : ILocalService - where TService : class - where TCallback : class -{ - private readonly ServerService _serverService; - - public LocalService(TService service) - { - _serverService = new ServerService(service); - } - - public LocalService() - { - var obj = this; - if (obj is TService service) - { - _serverService = new ServerService(service); - } - else - { - throw new InvalidOperationException( - $"'{GetType()}' must be implemented by '{typeof(TService)}'."); - } - } - - IService ILocalService.Service => _serverService; - - protected TCallback Callback => _serverService.Client; -} - -public class LocalService : ILocalService - where TService : class -{ - private readonly ServerService _serverService; - - public LocalService(TService service) - { - _serverService = new ServerService(service); - } - - public LocalService() - { - var obj = this; - if (obj is TService service) - { - _serverService = new ServerService(service); - } - else - { - throw new InvalidOperationException( - $"'{GetType()}' must be implemented by '{typeof(TService)}'."); - } - } - - IService ILocalService.Service => _serverService; -} +// // File may only contain a single type +// #pragma warning disable SA1402 +// using JSSoft.Communication; + +// namespace LibplanetConsole.Common.Services; + +// public class LocalService : ILocalService +// where TService : class +// where TCallback : class +// { +// private readonly ServerService _serverService; + +// public LocalService(TService service) +// { +// _serverService = new ServerService(service); +// } + +// public LocalService() +// { +// var obj = this; +// if (obj is TService service) +// { +// _serverService = new ServerService(service); +// } +// else +// { +// throw new InvalidOperationException( +// $"'{GetType()}' must be implemented by '{typeof(TService)}'."); +// } +// } + +// IService ILocalService.Service => _serverService; + +// protected TCallback Callback => _serverService.Client; +// } + +// public class LocalService : ILocalService +// where TService : class +// { +// private readonly ServerService _serverService; + +// public LocalService(TService service) +// { +// _serverService = new ServerService(service); +// } + +// public LocalService() +// { +// var obj = this; +// if (obj is TService service) +// { +// _serverService = new ServerService(service); +// } +// else +// { +// throw new InvalidOperationException( +// $"'{GetType()}' must be implemented by '{typeof(TService)}'."); +// } +// } + +// IService ILocalService.Service => _serverService; +// } diff --git a/src/common/LibplanetConsole.Common/Services/LocalServiceContext.cs b/src/common/LibplanetConsole.Common/Services/LocalServiceContext.cs index 4692b721..e944d3e2 100644 --- a/src/common/LibplanetConsole.Common/Services/LocalServiceContext.cs +++ b/src/common/LibplanetConsole.Common/Services/LocalServiceContext.cs @@ -1,69 +1,69 @@ -using JSSoft.Communication; +// using JSSoft.Communication; -namespace LibplanetConsole.Common.Services; +// namespace LibplanetConsole.Common.Services; -public class LocalServiceContext -{ - private readonly InternalServerContext _serverContext; - private EndPoint? _endPoint; +// public class LocalServiceContext +// { +// private readonly InternalServerContext _serverContext; +// private EndPoint? _endPoint; - public LocalServiceContext(IEnumerable localServices) - { - _serverContext = new([.. localServices.Select(service => service.Service)]); - _serverContext.Opened += (s, e) => Started?.Invoke(this, EventArgs.Empty); - _serverContext.Closed += (s, e) - => Stopped?.Invoke(this, new StopEventArgs(StopReason.None)); - _serverContext.Disconnected += (s, e) - => Stopped?.Invoke(this, new StopEventArgs(StopReason.Disconnected)); - _serverContext.Faulted += (s, e) - => Stopped?.Invoke(this, new StopEventArgs(StopReason.Faulted)); - } +// public LocalServiceContext(IEnumerable localServices) +// { +// _serverContext = new([.. localServices.Select(service => service.Service)]); +// _serverContext.Opened += (s, e) => Started?.Invoke(this, EventArgs.Empty); +// _serverContext.Closed += (s, e) +// => Stopped?.Invoke(this, new StopEventArgs(StopReason.None)); +// _serverContext.Disconnected += (s, e) +// => Stopped?.Invoke(this, new StopEventArgs(StopReason.Disconnected)); +// _serverContext.Faulted += (s, e) +// => Stopped?.Invoke(this, new StopEventArgs(StopReason.Faulted)); +// } - public event EventHandler? Started; +// public event EventHandler? Started; - public event EventHandler? Stopped; +// public event EventHandler? Stopped; - public EndPoint EndPoint - { - get => _endPoint ?? throw new InvalidOperationException("EndPoint is not set."); - set - { - _endPoint = value; - _serverContext.EndPoint = value; - } - } +// public EndPoint EndPoint +// { +// get => _endPoint ?? throw new InvalidOperationException("EndPoint is not set."); +// set +// { +// _endPoint = value; +// _serverContext.EndPoint = value; +// } +// } - public bool IsRunning => _serverContext.ServiceState == ServiceState.Open; +// public bool IsRunning => _serverContext.ServiceState == ServiceState.Open; - public async Task StartAsync(CancellationToken cancellationToken) - { - return await _serverContext.OpenAsync(cancellationToken); - } +// public async Task StartAsync(CancellationToken cancellationToken) +// { +// return await _serverContext.OpenAsync(cancellationToken); +// } - public Task StopAsync(Guid token) - => CloseAsync(token, CancellationToken.None); +// public Task StopAsync(Guid token) +// => CloseAsync(token, CancellationToken.None); - public async Task CloseAsync(Guid token, CancellationToken cancellationToken) - { - if (_serverContext.ServiceState == ServiceState.Open) - { - try - { - await _serverContext.CloseAsync(token, cancellationToken); - } - catch - { - // Ignore. - } - } +// public async Task CloseAsync(Guid token, CancellationToken cancellationToken) +// { +// if (_serverContext.ServiceState == ServiceState.Open) +// { +// try +// { +// await _serverContext.CloseAsync(token, cancellationToken); +// } +// catch +// { +// // Ignore. +// } +// } - if (_serverContext.ServiceState == ServiceState.Faulted) - { - await _serverContext.AbortAsync(); - } - } +// if (_serverContext.ServiceState == ServiceState.Faulted) +// { +// await _serverContext.AbortAsync(); +// } +// } - private sealed class InternalServerContext(IService[] services) : ServerContext(services) - { - } -} +// private sealed class InternalServerContext(IService[] services) : ServerContext(services) +// { +// } +// } diff --git a/src/common/LibplanetConsole.Common/Services/RemoteService.cs b/src/common/LibplanetConsole.Common/Services/RemoteService.cs index 934949c8..b7c134f8 100644 --- a/src/common/LibplanetConsole.Common/Services/RemoteService.cs +++ b/src/common/LibplanetConsole.Common/Services/RemoteService.cs @@ -1,50 +1,50 @@ -// File may only contain a single type -#pragma warning disable SA1402 -using JSSoft.Communication; - -namespace LibplanetConsole.Common.Services; - -public class RemoteService : IRemoteService - where TService : class - where TCallback : class -{ - private readonly ClientService _clientService; - - public RemoteService(TCallback callback) - { - _clientService = new ClientService(callback); - } - - public RemoteService() - { - var obj = this; - if (obj is TCallback callback) - { - _clientService = new ClientService(callback); - } - else - { - throw new InvalidOperationException( - $"'{GetType()}' must be implemented by '{typeof(TCallback)}'."); - } - } - - public TService Service => _clientService.Server; - - IService IRemoteService.Service => _clientService; -} - -public class RemoteService : IRemoteService - where TService : class -{ - private readonly ClientService _clientService; - - public RemoteService() - { - _clientService = new ClientService(); - } - - public TService Service => _clientService.Server; - - IService IRemoteService.Service => _clientService; -} +// // File may only contain a single type +// #pragma warning disable SA1402 +// using JSSoft.Communication; + +// namespace LibplanetConsole.Common.Services; + +// public class RemoteService : IRemoteService +// where TService : class +// where TCallback : class +// { +// private readonly ClientService _clientService; + +// public RemoteService(TCallback callback) +// { +// _clientService = new ClientService(callback); +// } + +// public RemoteService() +// { +// var obj = this; +// if (obj is TCallback callback) +// { +// _clientService = new ClientService(callback); +// } +// else +// { +// throw new InvalidOperationException( +// $"'{GetType()}' must be implemented by '{typeof(TCallback)}'."); +// } +// } + +// public TService Service => _clientService.Server; + +// IService IRemoteService.Service => _clientService; +// } + +// public class RemoteService : IRemoteService +// where TService : class +// { +// private readonly ClientService _clientService; + +// public RemoteService() +// { +// _clientService = new ClientService(); +// } + +// public TService Service => _clientService.Server; + +// IService IRemoteService.Service => _clientService; +// } diff --git a/src/common/LibplanetConsole.Common/Services/RemoteServiceContext.cs b/src/common/LibplanetConsole.Common/Services/RemoteServiceContext.cs index be5af2f8..6619fe6b 100644 --- a/src/common/LibplanetConsole.Common/Services/RemoteServiceContext.cs +++ b/src/common/LibplanetConsole.Common/Services/RemoteServiceContext.cs @@ -1,64 +1,64 @@ -using JSSoft.Communication; +// using JSSoft.Communication; -namespace LibplanetConsole.Common.Services; +// namespace LibplanetConsole.Common.Services; -public class RemoteServiceContext -{ - private readonly InternalClientContext _clientContext; - private EndPoint? _endPoint; +// public class RemoteServiceContext +// { +// private readonly InternalClientContext _clientContext; +// private EndPoint? _endPoint; - public RemoteServiceContext(IEnumerable remoteServices) - { - _clientContext = new([.. remoteServices.Select(service => service.Service)]); - _clientContext.Opened += (s, e) => Opened?.Invoke(this, EventArgs.Empty); - _clientContext.Closed += (s, e) => Closed?.Invoke(this, EventArgs.Empty); - } +// public RemoteServiceContext(IEnumerable remoteServices) +// { +// _clientContext = new([.. remoteServices.Select(service => service.Service)]); +// _clientContext.Opened += (s, e) => Opened?.Invoke(this, EventArgs.Empty); +// _clientContext.Closed += (s, e) => Closed?.Invoke(this, EventArgs.Empty); +// } - public event EventHandler? Opened; +// public event EventHandler? Opened; - public event EventHandler? Closed; +// public event EventHandler? Closed; - public EndPoint EndPoint - { - get => _endPoint ?? throw new InvalidOperationException("EndPoint is not set."); - set - { - _endPoint = value; - _clientContext.EndPoint = value; - } - } +// public EndPoint EndPoint +// { +// get => _endPoint ?? throw new InvalidOperationException("EndPoint is not set."); +// set +// { +// _endPoint = value; +// _clientContext.EndPoint = value; +// } +// } - public bool IsRunning => _clientContext.ServiceState == ServiceState.Open; +// public bool IsRunning => _clientContext.ServiceState == ServiceState.Open; - public async Task OpenAsync(CancellationToken cancellationToken) - { - return await _clientContext.OpenAsync(cancellationToken); - } +// public async Task OpenAsync(CancellationToken cancellationToken) +// { +// return await _clientContext.OpenAsync(cancellationToken); +// } - public Task CloseAsync(Guid token) - => CloseAsync(token, CancellationToken.None); +// public Task CloseAsync(Guid token) +// => CloseAsync(token, CancellationToken.None); - public async Task CloseAsync(Guid token, CancellationToken cancellationToken) - { - if (_clientContext.ServiceState == ServiceState.Open) - { - try - { - await _clientContext.CloseAsync(token, cancellationToken); - } - catch - { - // Ignore. - } - } +// public async Task CloseAsync(Guid token, CancellationToken cancellationToken) +// { +// if (_clientContext.ServiceState == ServiceState.Open) +// { +// try +// { +// await _clientContext.CloseAsync(token, cancellationToken); +// } +// catch +// { +// // Ignore. +// } +// } - if (_clientContext.ServiceState == ServiceState.Faulted) - { - await _clientContext.AbortAsync(); - } - } +// if (_clientContext.ServiceState == ServiceState.Faulted) +// { +// await _clientContext.AbortAsync(); +// } +// } - private sealed class InternalClientContext(IService[] services) : ClientContext(services) - { - } -} +// private sealed class InternalClientContext(IService[] services) : ClientContext(services) +// { +// } +// } diff --git a/src/console/LibplanetConsole.Console.Evidence/Evidence.cs b/src/console/LibplanetConsole.Console.Evidence/Evidence.cs index 15f074ce..3f602fb8 100644 --- a/src/console/LibplanetConsole.Console.Evidence/Evidence.cs +++ b/src/console/LibplanetConsole.Console.Evidence/Evidence.cs @@ -1,31 +1,39 @@ -using LibplanetConsole.Common.Services; -using LibplanetConsole.Console.Services; using LibplanetConsole.Evidence; using LibplanetConsole.Evidence.Services; namespace LibplanetConsole.Console.Evidence; internal sealed class Evidence(INode node) - : INodeContent, IEvidence, INodeContentService + : INodeContent, IEvidence + // , INodeContentService { - private readonly RemoteService _evidenceService = new(); + // private readonly RemoteService _evidenceService = new(); INode INodeContent.Node => node; string INodeContent.Name => "evidence"; - IRemoteService INodeContentService.RemoteService => _evidenceService; + // IRemoteService INodeContentService.RemoteService => _evidenceService; - private IEvidenceService Service => _evidenceService.Service; + // private IEvidenceService Service => _evidenceService.Service; public Task AddEvidenceAsync(CancellationToken cancellationToken) - => Service.AddEvidenceAsync(cancellationToken); + { + // return Service.AddEvidenceAsync(cancellationToken); + throw new NotImplementedException(); + } public Task GetEvidenceAsync(long height, CancellationToken cancellationToken) - => Service.GetEvidenceAsync(height, cancellationToken); + { + // return Service.GetEvidenceAsync(height, cancellationToken); + throw new NotImplementedException(); + } public Task ViolateAsync(CancellationToken cancellationToken) - => Service.ViolateAsync(cancellationToken); + { + // return Service.ViolateAsync(cancellationToken); + throw new NotImplementedException(); + } #if LIBPLANET_DPOS public Task UnjailAsync(CancellationToken cancellationToken) diff --git a/src/console/LibplanetConsole.Console.Example/ExampleClient.cs b/src/console/LibplanetConsole.Console.Example/ExampleClient.cs index 085e6acc..55d9ad6d 100644 --- a/src/console/LibplanetConsole.Console.Example/ExampleClient.cs +++ b/src/console/LibplanetConsole.Console.Example/ExampleClient.cs @@ -1,4 +1,3 @@ -using LibplanetConsole.Common.Services; using LibplanetConsole.Example.Services; namespace LibplanetConsole.Console.Example; @@ -6,13 +5,19 @@ namespace LibplanetConsole.Console.Example; internal sealed class ExampleClient(IClient client, ExampleClientSettings settings) : ClientContentBase(client), IExampleClient { - private readonly RemoteService _remoteService = new(); + // private readonly RemoteService _remoteService = new(); public bool IsExample { get; } = settings.IsClientExample; - private IExampleClientService Service => _remoteService.Service; + // private IExampleClientService Service => _remoteService.Service; - public void Subscribe() => Service.Subscribe(); + public void Subscribe() + { + // Service.Subscribe(); + } - public void Unsubscribe() => Service.Unsubscribe(); + public void Unsubscribe() + { + // Service.Unsubscribe(); + } } diff --git a/src/console/LibplanetConsole.Console.Example/ExampleNode.cs b/src/console/LibplanetConsole.Console.Example/ExampleNode.cs index c59a3911..79a29fa4 100644 --- a/src/console/LibplanetConsole.Console.Example/ExampleNode.cs +++ b/src/console/LibplanetConsole.Console.Example/ExampleNode.cs @@ -1,16 +1,15 @@ using System.Text; using LibplanetConsole.Common; -using LibplanetConsole.Common.Services; -using LibplanetConsole.Console.Services; using LibplanetConsole.Example.Services; namespace LibplanetConsole.Console.Example; internal sealed class ExampleNode(INode node, ExampleSettings settings) - : NodeContentBase(node), IExampleNodeCallback, IExampleNode, INodeContentService + : NodeContentBase(node), IExampleNodeCallback, IExampleNode + // , INodeContentService { private readonly StringBuilder _log = new(); - private RemoteService? _remoteService; + // private RemoteService? _remoteService; public event EventHandler>? Subscribed; @@ -20,30 +19,45 @@ internal sealed class ExampleNode(INode node, ExampleSettings settings) public bool IsExample { get; } = settings.IsNodeExample; - IRemoteService INodeContentService.RemoteService => RemoteService; + // IRemoteService INodeContentService.RemoteService => RemoteService; - private IExampleNodeService Service => RemoteService.Service; + // private IExampleNodeService Service => RemoteService.Service; - private RemoteService RemoteService - => _remoteService ??= new RemoteService(this); + // private RemoteService RemoteService + // { + // get + // { + // // return _remoteService ??= new RemoteService(this); + // throw new NotImplementedException(); + // } + // } public Task GetAddressesAsync(CancellationToken cancellationToken) - => Service.GetAddressesAsync(cancellationToken); + { + // return Service.GetAddressesAsync(cancellationToken); + throw new NotImplementedException(); + } - public void Subscribe(Address address) => Service.Subscribe(address); + public void Subscribe(Address address) + { + // Service.Subscribe(address); + } - public void Unsubscribe(Address address) => Service.Unsubscribe(address); + public void Unsubscribe(Address address) + { + // Service.Unsubscribe(address); + } async void IExampleNodeCallback.OnSubscribed(Address address) { - Count = await Service.GetAddressCountAsync(CancellationToken.None); + // Count = await Service.GetAddressCountAsync(CancellationToken.None); _log.AppendLine($"{nameof(IExampleNodeCallback.OnSubscribed)}: {address}"); Subscribed?.Invoke(this, new ItemEventArgs
(address)); } async void IExampleNodeCallback.OnUnsubscribed(Address address) { - Count = await Service.GetAddressCountAsync(CancellationToken.None); + // Count = await Service.GetAddressCountAsync(CancellationToken.None); _log.AppendLine($"{nameof(IExampleNodeCallback.OnUnsubscribed)}: {address}"); Unsubscribed?.Invoke(this, new ItemEventArgs
(address)); } diff --git a/src/console/LibplanetConsole.Console.Executable/ApplicationSettings.cs b/src/console/LibplanetConsole.Console.Executable/ApplicationSettings.cs index fd13b37e..db86a112 100644 --- a/src/console/LibplanetConsole.Console.Executable/ApplicationSettings.cs +++ b/src/console/LibplanetConsole.Console.Executable/ApplicationSettings.cs @@ -5,7 +5,6 @@ using LibplanetConsole.Common.DataAnnotations; using LibplanetConsole.DataAnnotations; using LibplanetConsole.Framework; -using LibplanetConsole.Node; namespace LibplanetConsole.Console.Executable; diff --git a/src/console/LibplanetConsole.Console.Explorer/Explorer.cs b/src/console/LibplanetConsole.Console.Explorer/Explorer.cs index 6f9bf6a2..fb3c2903 100644 --- a/src/console/LibplanetConsole.Console.Explorer/Explorer.cs +++ b/src/console/LibplanetConsole.Console.Explorer/Explorer.cs @@ -1,19 +1,18 @@ using LibplanetConsole.Common; -using LibplanetConsole.Common.Services; -using LibplanetConsole.Console.Services; using LibplanetConsole.Explorer; using LibplanetConsole.Explorer.Services; using Microsoft.Extensions.Logging; namespace LibplanetConsole.Console.Explorer; -internal sealed class Explorer : NodeContentBase, IExplorer, IExplorerCallback, INodeContentService +internal sealed class Explorer : NodeContentBase, IExplorer, IExplorerCallback +// , INodeContentService { private readonly ILogger _logger; private readonly ExecutionScope _executionScope = new(); private readonly ExplorerSettings _settings; private EndPoint _endPoint = EndPointUtility.NextEndPoint(); - private RemoteService? _remoteService; + // private RemoteService? _remoteService; public Explorer(INode node, ILogger logger, ExplorerSettings settings) : base(node) @@ -46,12 +45,12 @@ public EndPoint EndPoint public bool IsRunning { get; private set; } - IRemoteService INodeContentService.RemoteService => RemoteService; + // IRemoteService INodeContentService.RemoteService => RemoteService; - private IExplorerService Service => RemoteService.Service; + // private IExplorerService Service => RemoteService.Service; - private RemoteService RemoteService - => _remoteService ??= new RemoteService(this); + // private RemoteService RemoteService + // => _remoteService ??= new RemoteService(this); public async Task StartAsync(CancellationToken cancellationToken) { @@ -67,7 +66,7 @@ public async Task StartAsync(CancellationToken cancellationToken) { EndPoint = EndPoint, }; - Info = await Service.StartAsync(options, cancellationToken); + // Info = await Service.StartAsync(options, cancellationToken); IsRunning = true; _logger.LogDebug("Explorer is started: {NodeAddress} {EndPoint}", nodeAddress, endPoint); Started?.Invoke(this, EventArgs.Empty); @@ -81,7 +80,7 @@ public async Task StopAsync(CancellationToken cancellationToken) } using var scope = _executionScope.Enter(); - await Service.StopAsync(cancellationToken); + // await Service.StopAsync(cancellationToken); IsRunning = false; _logger.LogDebug("Explorer is stopped.: {NodeAddress}", Node.Address); Stopped?.Invoke(this, EventArgs.Empty); @@ -114,7 +113,7 @@ void IExplorerCallback.OnStopped() protected async override void OnNodeAttached() { base.OnNodeAttached(); - Info = await Service.GetInfoAsync(cancellationToken: default); + // Info = await Service.GetInfoAsync(cancellationToken: default); IsRunning = Info.IsRunning; } diff --git a/src/console/LibplanetConsole.Console.Explorer/ServiceCollectionExtensions.cs b/src/console/LibplanetConsole.Console.Explorer/ServiceCollectionExtensions.cs index 54d7edd7..5db9f26b 100644 --- a/src/console/LibplanetConsole.Console.Explorer/ServiceCollectionExtensions.cs +++ b/src/console/LibplanetConsole.Console.Explorer/ServiceCollectionExtensions.cs @@ -1,7 +1,6 @@ using JSSoft.Commands; using LibplanetConsole.Common; using LibplanetConsole.Console.Explorer.Commands; -using LibplanetConsole.Console.Services; using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Console.Explorer; @@ -12,8 +11,8 @@ public static IServiceCollection AddExplorer(this IServiceCollection @this) { @this.AddScoped() .AddScoped(s => s.GetRequiredService()) - .AddScoped(s => s.GetRequiredService()) - .AddScoped(s => s.GetRequiredService()); + .AddScoped(s => s.GetRequiredService()); + // .AddScoped(s => s.GetRequiredService()); @this.AddSingleton(); diff --git a/src/console/LibplanetConsole.Console/ApplicationBase.cs b/src/console/LibplanetConsole.Console/ApplicationBase.cs index feefeb56..2003dc97 100644 --- a/src/console/LibplanetConsole.Console/ApplicationBase.cs +++ b/src/console/LibplanetConsole.Console/ApplicationBase.cs @@ -13,7 +13,7 @@ public abstract class ApplicationBase : ApplicationFramework, IApplication private readonly ClientCollection _clients; private readonly ApplicationInfo _info; private readonly ILogger _logger; - private ConsoleServiceContext? _consoleContext; + // private ConsoleServiceContext? _consoleContext; private Guid _closeToken; protected ApplicationBase(IServiceProvider serviceProvider, ApplicationOptions options) diff --git a/src/console/LibplanetConsole.Console/Client.cs b/src/console/LibplanetConsole.Console/Client.cs index 68c88ab0..fe99a67c 100644 --- a/src/console/LibplanetConsole.Console/Client.cs +++ b/src/console/LibplanetConsole.Console/Client.cs @@ -1,10 +1,7 @@ using LibplanetConsole.Client; using LibplanetConsole.Client.Services; -using LibplanetConsole.Common; using LibplanetConsole.Common.Exceptions; using LibplanetConsole.Common.Extensions; -using LibplanetConsole.Common.Services; -using LibplanetConsole.Console.Services; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -14,10 +11,10 @@ internal sealed class Client : IClient, IClientCallback { private readonly IServiceProvider _serviceProvider; private readonly ClientOptions _clientOptions; - private readonly RemoteService _remoteService; + // private readonly RemoteService _remoteService; private readonly ILogger _logger; private readonly CancellationTokenSource _processCancellationTokenSource = new(); - private RemoteServiceContext? _remoteServiceContext; + // private RemoteServiceContext? _remoteServiceContext; private Guid _closeToken; private ClientInfo _clientInfo; private bool _isDisposed; @@ -68,8 +65,9 @@ public async Task GetInfoAsync(CancellationToken cancellationToken) ObjectDisposedException.ThrowIf(_isDisposed, this); InvalidOperationExceptionUtility.ThrowIf(IsRunning is not true, "Client is not running."); - _clientInfo = await _remoteService.Service.GetInfoAsync(cancellationToken); - return _clientInfo; + // _clientInfo = await _remoteService.Service.GetInfoAsync(cancellationToken); + // return _clientInfo; + throw new NotImplementedException(); } public async Task AttachAsync(CancellationToken cancellationToken) @@ -79,20 +77,20 @@ public async Task AttachAsync(CancellationToken cancellationToken) condition: _closeToken != Guid.Empty, message: "Client is already attached."); - if (_remoteServiceContext is not null) - { - throw new InvalidOperationException("Client is already attached."); - } + // if (_remoteServiceContext is not null) + // { + // throw new InvalidOperationException("Client is already attached."); + // } using var scope = new ProgressScope(this); - _remoteServiceContext = new RemoteServiceContext( - [_remoteService, .. GetRemoteServices(_serviceProvider)]) - { - EndPoint = _clientOptions.EndPoint, - }; - _closeToken = await _remoteServiceContext.OpenAsync(cancellationToken); - _remoteServiceContext.Closed += RemoteServiceContext_Closed; - _clientInfo = await _remoteService.Service.GetInfoAsync(cancellationToken); + // _remoteServiceContext = new RemoteServiceContext( + // [_remoteService, .. GetRemoteServices(_serviceProvider)]) + // { + // EndPoint = _clientOptions.EndPoint, + // }; + // _closeToken = await _remoteServiceContext.OpenAsync(cancellationToken); + // _remoteServiceContext.Closed += RemoteServiceContext_Closed; + // _clientInfo = await _remoteService.Service.GetInfoAsync(cancellationToken); IsRunning = _clientInfo.IsRunning; _logger.LogDebug("Client is attached: {Address}", Address); Attached?.Invoke(this, EventArgs.Empty); @@ -105,14 +103,14 @@ public async Task DetachAsync(CancellationToken cancellationToken) condition: _closeToken == Guid.Empty, message: "Client is not attached."); - if (_remoteServiceContext is null) - { - throw new InvalidOperationException("Client is not attached."); - } + // if (_remoteServiceContext is null) + // { + // throw new InvalidOperationException("Client is not attached."); + // } - using var scope = new ProgressScope(this); - _remoteServiceContext.Closed -= RemoteServiceContext_Closed; - await _remoteServiceContext.CloseAsync(_closeToken, cancellationToken); + // using var scope = new ProgressScope(this); + // _remoteServiceContext.Closed -= RemoteServiceContext_Closed; + // await _remoteServiceContext.CloseAsync(_closeToken, cancellationToken); _closeToken = Guid.Empty; _logger.LogDebug("Client is detached: {Address}", Address); Detached?.Invoke(this, EventArgs.Empty); @@ -126,8 +124,8 @@ public async Task StartAsync(INode node, CancellationToken cancellationToken) condition: _closeToken == Guid.Empty, message: "Client is not attached."); - _clientInfo = await _remoteService.Service.StartAsync( - EndPointUtility.ToString(node.EndPoint), cancellationToken); + // _clientInfo = await _remoteService.Service.StartAsync( + // EndPointUtility.ToString(node.EndPoint), cancellationToken); IsRunning = true; _logger.LogDebug("Client is started: {Address}", Address); Started?.Invoke(this, EventArgs.Empty); @@ -141,7 +139,7 @@ public async Task StopAsync(CancellationToken cancellationToken) condition: _closeToken == Guid.Empty, message: "Client is not attached."); - await _remoteService.Service.StopAsync(cancellationToken); + // await _remoteService.Service.StopAsync(cancellationToken); _closeToken = Guid.Empty; _clientInfo = default; IsRunning = false; @@ -155,8 +153,9 @@ public async Task SendTransactionAsync(string text, CancellationToken canc { Text = text, }; - return await _remoteService.Service.SendTransactionAsync( - transactionOptions.Sign(this), cancellationToken); + // return await _remoteService.Service.SendTransactionAsync( + // transactionOptions.Sign(this), cancellationToken); + throw new NotImplementedException(); } public async ValueTask DisposeAsync() @@ -169,12 +168,12 @@ public async ValueTask DisposeAsync() _processTask = Task.CompletedTask; _process = null; - if (_remoteServiceContext is not null) - { - _remoteServiceContext.Closed -= RemoteServiceContext_Closed; - await _remoteServiceContext.CloseAsync(_closeToken); - _remoteServiceContext = null; - } + // if (_remoteServiceContext is not null) + // { + // _remoteServiceContext.Closed -= RemoteServiceContext_Closed; + // await _remoteServiceContext.CloseAsync(_closeToken); + // _remoteServiceContext = null; + // } _closeToken = Guid.Empty; IsRunning = false; @@ -236,28 +235,28 @@ void IClientCallback.OnStopped() } } - private static IEnumerable GetRemoteServices( - IServiceProvider serviceProvider) - { - foreach (var item in serviceProvider.GetServices()) - { - yield return item.RemoteService; - } - } - - private void RemoteServiceContext_Closed(object? sender, EventArgs e) - { - if (sender is RemoteServiceContext remoteServiceContext) - { - remoteServiceContext.Closed -= RemoteServiceContext_Closed; - _remoteServiceContext = null; - if (_isInProgress is not true && IsRunning is true) - { - _closeToken = Guid.Empty; - Detached?.Invoke(this, EventArgs.Empty); - } - } - } + // private static IEnumerable GetRemoteServices( + // IServiceProvider serviceProvider) + // { + // foreach (var item in serviceProvider.GetServices()) + // { + // yield return item.RemoteService; + // } + // } + + // private void RemoteServiceContext_Closed(object? sender, EventArgs e) + // { + // if (sender is RemoteServiceContext remoteServiceContext) + // { + // remoteServiceContext.Closed -= RemoteServiceContext_Closed; + // _remoteServiceContext = null; + // if (_isInProgress is not true && IsRunning is true) + // { + // _closeToken = Guid.Empty; + // Detached?.Invoke(this, EventArgs.Empty); + // } + // } + // } private sealed class ProgressScope : IDisposable { diff --git a/src/console/LibplanetConsole.Console/ConsoleServiceContext.cs b/src/console/LibplanetConsole.Console/ConsoleServiceContext.cs index d9c3db15..a46e711e 100644 --- a/src/console/LibplanetConsole.Console/ConsoleServiceContext.cs +++ b/src/console/LibplanetConsole.Console/ConsoleServiceContext.cs @@ -1,8 +1,8 @@ -using LibplanetConsole.Common.Services; +// using LibplanetConsole.Common.Services; -namespace LibplanetConsole.Console; +// namespace LibplanetConsole.Console; -internal sealed class ConsoleServiceContext( - IEnumerable localServices) : LocalServiceContext([.. localServices]) -{ -} +// internal sealed class ConsoleServiceContext( +// IEnumerable localServices) : LocalServiceContext([.. localServices]) +// { +// } diff --git a/src/console/LibplanetConsole.Console/Node.BlockChain.cs b/src/console/LibplanetConsole.Console/Node.BlockChain.cs index 44610d71..5f5a42a6 100644 --- a/src/console/LibplanetConsole.Console/Node.BlockChain.cs +++ b/src/console/LibplanetConsole.Console/Node.BlockChain.cs @@ -9,32 +9,37 @@ internal sealed partial class Node public event EventHandler? BlockAppended; public Task GetNextNonceAsync(Address address, CancellationToken cancellationToken) - => _blockChainService.Service.GetNextNonceAsync(address, cancellationToken); + { + // return _blockChainService.Service.GetNextNonceAsync(address, cancellationToken); + throw new NotImplementedException(); + } public async Task SendTransactionAsync( IAction[] actions, CancellationToken cancellationToken) { - var privateKey = _nodeOptions.PrivateKey; - var address = privateKey.Address; - var nonce = await _blockChainService.Service.GetNextNonceAsync(address, cancellationToken); - var genesisHash = _nodeInfo.GenesisHash; - var tx = Transaction.Create( - nonce: nonce, - privateKey: privateKey, - genesisHash: genesisHash, - actions: [.. actions.Select(item => item.PlainValue)]); - var txId = await _blockChainService.Service.SendTransactionAsync( - transaction: tx.Serialize(), - cancellationToken: cancellationToken); + // var privateKey = _nodeOptions.PrivateKey; + // var address = privateKey.Address; + // var nonce = await _blockChainService.Service.GetNextNonceAsync(address, cancellationToken); + // var genesisHash = _nodeInfo.GenesisHash; + // var tx = Transaction.Create( + // nonce: nonce, + // privateKey: privateKey, + // genesisHash: genesisHash, + // actions: [.. actions.Select(item => item.PlainValue)]); + // var txId = await _blockChainService.Service.SendTransactionAsync( + // transaction: tx.Serialize(), + // cancellationToken: cancellationToken); - return txId; + // return txId; + throw new NotImplementedException(); } public Task SendTransactionAsync( Transaction transaction, CancellationToken cancellationToken) { - return _blockChainService.Service.SendTransactionAsync( - transaction.Serialize(), cancellationToken); + // return _blockChainService.Service.SendTransactionAsync( + // transaction.Serialize(), cancellationToken); + throw new NotImplementedException(); } void IBlockChainCallback.OnBlockAppended(BlockInfo blockInfo) @@ -44,7 +49,10 @@ void IBlockChainCallback.OnBlockAppended(BlockInfo blockInfo) } public Task GetTipHashAsync(CancellationToken cancellationToken) - => _blockChainService.Service.GetTipHashAsync(cancellationToken); + { + // return _blockChainService.Service.GetTipHashAsync(cancellationToken); + throw new NotImplementedException(); + } public async Task GetStateAsync( BlockHash? blockHash, @@ -52,12 +60,13 @@ public async Task GetStateAsync( Address address, CancellationToken cancellationToken) { - var bytes = await _blockChainService.Service.GetStateAsync( - blockHash, - accountAddress, - address, - cancellationToken); - return _codec.Decode(bytes); + // var bytes = await _blockChainService.Service.GetStateAsync( + // blockHash, + // accountAddress, + // address, + // cancellationToken); + // return _codec.Decode(bytes); + throw new NotImplementedException(); } public async Task GetStateByStateRootHashAsync( @@ -66,16 +75,20 @@ public async Task GetStateByStateRootHashAsync( Address address, CancellationToken cancellationToken) { - var bytes = await _blockChainService.Service.GetStateByStateRootHashAsync( - stateRootHash, - accountAddress, - address, - cancellationToken); - return _codec.Decode(bytes); + // var bytes = await _blockChainService.Service.GetStateByStateRootHashAsync( + // stateRootHash, + // accountAddress, + // address, + // cancellationToken); + // return _codec.Decode(bytes); + throw new NotImplementedException(); } public Task GetBlockHashAsync(long height, CancellationToken cancellationToken) - => _blockChainService.Service.GetBlockHashAsync(height, cancellationToken); + { + // return _blockChainService.Service.GetBlockHashAsync(height, cancellationToken); + throw new NotImplementedException(); + } public async Task GetActionAsync( TxId txId, @@ -83,15 +96,16 @@ public async Task GetActionAsync( CancellationToken cancellationToken) where T : IAction { - var bytes = await _blockChainService.Service.GetActionAsync( - txId, actionIndex, cancellationToken); - var value = _codec.Decode(bytes); - if (Activator.CreateInstance(typeof(T)) is T action) - { - action.LoadPlainValue(value); - return action; - } + // var bytes = await _blockChainService.Service.GetActionAsync( + // txId, actionIndex, cancellationToken); + // var value = _codec.Decode(bytes); + // if (Activator.CreateInstance(typeof(T)) is T action) + // { + // action.LoadPlainValue(value); + // return action; + // } - throw new InvalidOperationException("Action not found."); + // throw new InvalidOperationException("Action not found."); + throw new NotImplementedException(); } } diff --git a/src/console/LibplanetConsole.Console/Node.cs b/src/console/LibplanetConsole.Console/Node.cs index c59690f7..1f3f54b8 100644 --- a/src/console/LibplanetConsole.Console/Node.cs +++ b/src/console/LibplanetConsole.Console/Node.cs @@ -1,8 +1,6 @@ using LibplanetConsole.Common; using LibplanetConsole.Common.Exceptions; using LibplanetConsole.Common.Extensions; -using LibplanetConsole.Common.Services; -using LibplanetConsole.Console.Services; using LibplanetConsole.Node; using LibplanetConsole.Node.Services; using Microsoft.Extensions.DependencyInjection; @@ -15,11 +13,11 @@ internal sealed partial class Node : INode, IBlockChain, INodeCallback, IBlockCh private static readonly Codec _codec = new(); private readonly IServiceProvider _serviceProvider; private readonly NodeOptions _nodeOptions; - private readonly RemoteService _remoteService; - private readonly RemoteService _blockChainService; + // private readonly RemoteService _remoteService; + // private readonly RemoteService _blockChainService; private readonly ILogger _logger; private readonly CancellationTokenSource _processCancellationTokenSource = new(); - private RemoteServiceContext? _remoteServiceContext; + // private RemoteServiceContext? _remoteServiceContext; private EndPoint? _blocksyncEndPoint; private EndPoint? _consensusEndPoint; private Guid _closeToken; @@ -79,8 +77,9 @@ public async Task GetInfoAsync(CancellationToken cancellationToken) ObjectDisposedException.ThrowIf(_isDisposed, this); InvalidOperationExceptionUtility.ThrowIf(IsRunning != true, "Node is not running."); - _nodeInfo = await _remoteService.Service.GetInfoAsync(cancellationToken); - return _nodeInfo; + // _nodeInfo = await _remoteService.Service.GetInfoAsync(cancellationToken); + // return _nodeInfo; + throw new NotImplementedException(); } public async Task AttachAsync(CancellationToken cancellationToken) @@ -90,21 +89,21 @@ public async Task AttachAsync(CancellationToken cancellationToken) condition: _closeToken != Guid.Empty, message: "Node is already attached."); - if (_remoteServiceContext is not null) - { - throw new InvalidOperationException("Node is already attached."); - } + // if (_remoteServiceContext is not null) + // { + // throw new InvalidOperationException("Node is already attached."); + // } - _remoteServiceContext = new RemoteServiceContext( - [_remoteService, _blockChainService, .. GetRemoteServices(_serviceProvider)]) - { - EndPoint = _nodeOptions.EndPoint, - }; - _remoteServiceContext.Closed += RemoteServiceContext_Closed; + // _remoteServiceContext = new RemoteServiceContext( + // [_remoteService, _blockChainService, .. GetRemoteServices(_serviceProvider)]) + // { + // EndPoint = _nodeOptions.EndPoint, + // }; + // _remoteServiceContext.Closed += RemoteServiceContext_Closed; using var scope = new ProgressScope(this); - _closeToken = await _remoteServiceContext.OpenAsync(cancellationToken); - _nodeInfo = await _remoteService.Service.GetInfoAsync(cancellationToken); + // _closeToken = await _remoteServiceContext.OpenAsync(cancellationToken); + // _nodeInfo = await _remoteService.Service.GetInfoAsync(cancellationToken); IsRunning = _nodeInfo.IsRunning; _logger.LogDebug("Node is attached: {Address}", Address); Attached?.Invoke(this, EventArgs.Empty); @@ -117,15 +116,15 @@ public async Task DetachAsync(CancellationToken cancellationToken) condition: _closeToken == Guid.Empty, message: "Node is not attached."); - if (_remoteServiceContext is null) - { - throw new InvalidOperationException("Node is not attached."); - } + // if (_remoteServiceContext is null) + // { + // throw new InvalidOperationException("Node is not attached."); + // } using var scope = new ProgressScope(this); - _remoteServiceContext.Closed -= RemoteServiceContext_Closed; - await _remoteServiceContext.CloseAsync(_closeToken, cancellationToken); - _remoteServiceContext = null; + // _remoteServiceContext.Closed -= RemoteServiceContext_Closed; + // await _remoteServiceContext.CloseAsync(_closeToken, cancellationToken); + // _remoteServiceContext = null; _closeToken = Guid.Empty; _logger.LogDebug("Node is detached: {Address}", Address); Detached?.Invoke(this, EventArgs.Empty); @@ -143,7 +142,7 @@ public async Task StartAsync(CancellationToken cancellationToken) var application = this.GetRequiredService(); var seedEndPoint = EndPointUtility.ToString( _nodeOptions.SeedEndPoint ?? application.Info.EndPoint); - _nodeInfo = await _remoteService.Service.StartAsync(seedEndPoint, cancellationToken); + // _nodeInfo = await _remoteService.Service.StartAsync(seedEndPoint, cancellationToken); _blocksyncEndPoint = EndPointUtility.Parse(_nodeInfo.SwarmEndPoint); _consensusEndPoint = EndPointUtility.Parse(_nodeInfo.ConsensusEndPoint); IsRunning = true; @@ -160,7 +159,7 @@ public async Task StopAsync(CancellationToken cancellationToken) message: "Node is not attached."); using var scope = new ProgressScope(this); - await _remoteService.Service.StopAsync(cancellationToken); + // await _remoteService.Service.StopAsync(cancellationToken); _closeToken = Guid.Empty; _nodeInfo = NodeInfo.Empty; IsRunning = false; @@ -178,12 +177,12 @@ public async ValueTask DisposeAsync() _processTask = Task.CompletedTask; _process = null; - if (_remoteServiceContext is not null) - { - _remoteServiceContext.Closed -= RemoteServiceContext_Closed; - await _remoteServiceContext.CloseAsync(_closeToken); - _remoteServiceContext = null; - } + // if (_remoteServiceContext is not null) + // { + // _remoteServiceContext.Closed -= RemoteServiceContext_Closed; + // await _remoteServiceContext.CloseAsync(_closeToken); + // _remoteServiceContext = null; + // } _closeToken = Guid.Empty; IsRunning = false; @@ -243,28 +242,28 @@ void INodeCallback.OnStopped() } } - private static IEnumerable GetRemoteServices( - IServiceProvider serviceProvider) - { - foreach (var item in serviceProvider.GetServices()) - { - yield return item.RemoteService; - } - } - - private void RemoteServiceContext_Closed(object? sender, EventArgs e) - { - if (sender is RemoteServiceContext remoteServiceContext) - { - remoteServiceContext.Closed -= RemoteServiceContext_Closed; - _remoteServiceContext = null; - if (_isInProgress != true && IsRunning == true) - { - _closeToken = Guid.Empty; - Detached?.Invoke(this, EventArgs.Empty); - } - } - } + // private static IEnumerable GetRemoteServices( + // IServiceProvider serviceProvider) + // { + // foreach (var item in serviceProvider.GetServices()) + // { + // yield return item.RemoteService; + // } + // } + + // private void RemoteServiceContext_Closed(object? sender, EventArgs e) + // { + // if (sender is RemoteServiceContext remoteServiceContext) + // { + // remoteServiceContext.Closed -= RemoteServiceContext_Closed; + // _remoteServiceContext = null; + // if (_isInProgress != true && IsRunning == true) + // { + // _closeToken = Guid.Empty; + // Detached?.Invoke(this, EventArgs.Empty); + // } + // } + // } private sealed class ProgressScope : IDisposable { diff --git a/src/console/LibplanetConsole.Console/NodeCollection.cs b/src/console/LibplanetConsole.Console/NodeCollection.cs index 76ac8662..bd829388 100644 --- a/src/console/LibplanetConsole.Console/NodeCollection.cs +++ b/src/console/LibplanetConsole.Console/NodeCollection.cs @@ -1,14 +1,13 @@ using System.Collections; using System.Collections.Specialized; using LibplanetConsole.Common.Extensions; -using LibplanetConsole.Console.Services; using LibplanetConsole.Framework; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace LibplanetConsole.Console; -[Dependency(typeof(SeedService))] +// [Dependency(typeof(SeedService))] internal sealed class NodeCollection( IServiceProvider serviceProvider, NodeOptions[] nodeOptions) : IEnumerable, INodeCollection, IApplicationService, IAsyncDisposable diff --git a/src/console/LibplanetConsole.Console/ServiceCollectionExtensions.cs b/src/console/LibplanetConsole.Console/ServiceCollectionExtensions.cs index 05930930..5fd402b2 100644 --- a/src/console/LibplanetConsole.Console/ServiceCollectionExtensions.cs +++ b/src/console/LibplanetConsole.Console/ServiceCollectionExtensions.cs @@ -1,8 +1,6 @@ using JSSoft.Commands; using LibplanetConsole.Common; -using LibplanetConsole.Common.Services; using LibplanetConsole.Console.Commands; -using LibplanetConsole.Console.Services; using LibplanetConsole.Framework; using Microsoft.Extensions.DependencyInjection; @@ -27,10 +25,10 @@ public static IServiceCollection AddConsole( @this.AddScoped(ClientFactory.Create) .AddScoped(s => s.GetRequiredService()); - @this.AddSingleton(); - @this.AddSingleton() - .AddSingleton(s => s.GetRequiredService()) - .AddSingleton(s => s.GetRequiredService()); + // @this.AddSingleton(); + // @this.AddSingleton() + // .AddSingleton(s => s.GetRequiredService()) + // .AddSingleton(s => s.GetRequiredService()); @this.AddSingleton(); @this.AddSingleton(); diff --git a/src/console/LibplanetConsole.Console/Services/IClientContentService.cs b/src/console/LibplanetConsole.Console/Services/IClientContentService.cs index efb44ea5..c6ee30b8 100644 --- a/src/console/LibplanetConsole.Console/Services/IClientContentService.cs +++ b/src/console/LibplanetConsole.Console/Services/IClientContentService.cs @@ -1,8 +1,8 @@ -using LibplanetConsole.Common.Services; +// using LibplanetConsole.Common.Services; -namespace LibplanetConsole.Console.Services; +// namespace LibplanetConsole.Console.Services; -public interface IClientContentService -{ - IRemoteService RemoteService { get; } -} +// public interface IClientContentService +// { +// IRemoteService RemoteService { get; } +// } diff --git a/src/console/LibplanetConsole.Console/Services/INodeContentService.cs b/src/console/LibplanetConsole.Console/Services/INodeContentService.cs index 0b27fa0d..37ce3bc8 100644 --- a/src/console/LibplanetConsole.Console/Services/INodeContentService.cs +++ b/src/console/LibplanetConsole.Console/Services/INodeContentService.cs @@ -1,8 +1,8 @@ -using LibplanetConsole.Common.Services; +// using LibplanetConsole.Common.Services; -namespace LibplanetConsole.Console.Services; +// namespace LibplanetConsole.Console.Services; -public interface INodeContentService -{ - IRemoteService RemoteService { get; } -} +// public interface INodeContentService +// { +// IRemoteService RemoteService { get; } +// } diff --git a/src/console/LibplanetConsole.Console/Services/SeedService.cs b/src/console/LibplanetConsole.Console/Services/SeedService.cs index d509bf5e..99c2965e 100644 --- a/src/console/LibplanetConsole.Console/Services/SeedService.cs +++ b/src/console/LibplanetConsole.Console/Services/SeedService.cs @@ -1,64 +1,64 @@ -using Libplanet.Net; -using LibplanetConsole.Common; -using LibplanetConsole.Common.Services; -using LibplanetConsole.Framework; -using LibplanetConsole.Seed; +// using Libplanet.Net; +// using LibplanetConsole.Common; +// using LibplanetConsole.Common.Services; +// using LibplanetConsole.Framework; +// using LibplanetConsole.Seed; -namespace LibplanetConsole.Console.Services; +// namespace LibplanetConsole.Console.Services; -internal sealed class SeedService : LocalService, - ISeedService, IApplicationService, IAsyncDisposable -{ - private readonly PrivateKey _seedNodePrivateKey = new(); - private readonly SeedNode _blocksyncSeedNode; - private readonly SeedNode _consensusSeedNode; - private bool _isDisposed; +// internal sealed class SeedService : LocalService, +// ISeedService, IApplicationService, IAsyncDisposable +// { +// private readonly PrivateKey _seedNodePrivateKey = new(); +// private readonly SeedNode _blocksyncSeedNode; +// private readonly SeedNode _consensusSeedNode; +// private bool _isDisposed; - public SeedService(IApplication application) - { - _blocksyncSeedNode = new SeedNode(new() - { - PrivateKey = _seedNodePrivateKey, - EndPoint = EndPointUtility.NextEndPoint(), - }); - _consensusSeedNode = new SeedNode(new() - { - PrivateKey = _seedNodePrivateKey, - EndPoint = EndPointUtility.NextEndPoint(), - }); - } +// public SeedService(IApplication application) +// { +// _blocksyncSeedNode = new SeedNode(new() +// { +// PrivateKey = _seedNodePrivateKey, +// EndPoint = EndPointUtility.NextEndPoint(), +// }); +// _consensusSeedNode = new SeedNode(new() +// { +// PrivateKey = _seedNodePrivateKey, +// EndPoint = EndPointUtility.NextEndPoint(), +// }); +// } - public BoundPeer BlocksyncSeedPeer => _blocksyncSeedNode.BoundPeer; +// public BoundPeer BlocksyncSeedPeer => _blocksyncSeedNode.BoundPeer; - public BoundPeer ConsensusSeedPeer => _consensusSeedNode.BoundPeer; +// public BoundPeer ConsensusSeedPeer => _consensusSeedNode.BoundPeer; - public async Task GetSeedAsync( - PublicKey publicKey, CancellationToken cancellationToken) - { - var seedPeer = _blocksyncSeedNode.BoundPeer; - var consensusSeedPeer = _consensusSeedNode.BoundPeer; - var seedInfo = new SeedInfo - { - BlocksyncSeedPeer = seedPeer, - ConsensusSeedPeer = consensusSeedPeer, - }; +// public async Task GetSeedAsync( +// PublicKey publicKey, CancellationToken cancellationToken) +// { +// var seedPeer = _blocksyncSeedNode.BoundPeer; +// var consensusSeedPeer = _consensusSeedNode.BoundPeer; +// var seedInfo = new SeedInfo +// { +// BlocksyncSeedPeer = seedPeer, +// ConsensusSeedPeer = consensusSeedPeer, +// }; - return await Task.Run(() => seedInfo, cancellationToken); - } +// return await Task.Run(() => seedInfo, cancellationToken); +// } - async Task IApplicationService.InitializeAsync(CancellationToken cancellationToken) - { - await _blocksyncSeedNode.StartAsync(cancellationToken); - await _consensusSeedNode.StartAsync(cancellationToken); - } +// async Task IApplicationService.InitializeAsync(CancellationToken cancellationToken) +// { +// await _blocksyncSeedNode.StartAsync(cancellationToken); +// await _consensusSeedNode.StartAsync(cancellationToken); +// } - async ValueTask IAsyncDisposable.DisposeAsync() - { - if (_isDisposed is false) - { - await _blocksyncSeedNode.StopAsync(cancellationToken: default); - await _consensusSeedNode.StopAsync(cancellationToken: default); - _isDisposed = true; - } - } -} +// async ValueTask IAsyncDisposable.DisposeAsync() +// { +// if (_isDisposed is false) +// { +// await _blocksyncSeedNode.StopAsync(cancellationToken: default); +// await _consensusSeedNode.StopAsync(cancellationToken: default); +// _isDisposed = true; +// } +// } +// } diff --git a/src/node/LibplanetConsole.Node.Evidence/ServiceCollectionExtensions.cs b/src/node/LibplanetConsole.Node.Evidence/ServiceCollectionExtensions.cs index 056bf077..8eb9bf5e 100644 --- a/src/node/LibplanetConsole.Node.Evidence/ServiceCollectionExtensions.cs +++ b/src/node/LibplanetConsole.Node.Evidence/ServiceCollectionExtensions.cs @@ -1,7 +1,5 @@ using JSSoft.Commands; -using LibplanetConsole.Common.Services; using LibplanetConsole.Node.Evidence.Commands; -using LibplanetConsole.Node.Evidence.Services; using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Node.Evidence; @@ -12,7 +10,7 @@ public static IServiceCollection AddEvidence(this IServiceCollection @this) { @this.AddSingleton() .AddSingleton(s => s.GetRequiredService()); - @this.AddSingleton(); + // @this.AddSingleton(); @this.AddSingleton(); return @this; } diff --git a/src/node/LibplanetConsole.Node.Evidence/Services/EvidenceService.cs b/src/node/LibplanetConsole.Node.Evidence/Services/EvidenceService.cs index 9fabf280..b0dd6ebf 100644 --- a/src/node/LibplanetConsole.Node.Evidence/Services/EvidenceService.cs +++ b/src/node/LibplanetConsole.Node.Evidence/Services/EvidenceService.cs @@ -1,30 +1,29 @@ -using LibplanetConsole.Common.Services; -using LibplanetConsole.Evidence; -using LibplanetConsole.Evidence.Services; +// using LibplanetConsole.Evidence; +// using LibplanetConsole.Evidence.Services; -namespace LibplanetConsole.Node.Evidence.Services; +// namespace LibplanetConsole.Node.Evidence.Services; -internal sealed class EvidenceService(Evidence evidence) - : LocalService, IEvidenceService -{ - public Task AddEvidenceAsync(CancellationToken cancellationToken) - => evidence.AddEvidenceAsync(cancellationToken); +// internal sealed class EvidenceService(Evidence evidence) +// : LocalService, IEvidenceService +// { +// public Task AddEvidenceAsync(CancellationToken cancellationToken) +// => evidence.AddEvidenceAsync(cancellationToken); - public Task GetEvidenceAsync(long height, CancellationToken cancellationToken) - => evidence.GetEvidenceAsync(height, cancellationToken); +// public Task GetEvidenceAsync(long height, CancellationToken cancellationToken) +// => evidence.GetEvidenceAsync(height, cancellationToken); - public Task ViolateAsync(CancellationToken cancellationToken) - => evidence.ViolateAsync(cancellationToken); +// public Task ViolateAsync(CancellationToken cancellationToken) +// => evidence.ViolateAsync(cancellationToken); -#if LIBPLANET_DPOS - public async Task UnjailAsync(byte[] signarue, CancellationToken cancellationToken) - { - if (node.Verify(true, signarue) == true) - { - await evidence.UnjailAsync(cancellationToken); - } +// #if LIBPLANET_DPOS +// public async Task UnjailAsync(byte[] signarue, CancellationToken cancellationToken) +// { +// if (node.Verify(true, signarue) == true) +// { +// await evidence.UnjailAsync(cancellationToken); +// } - throw new ArgumentException("Invalid signature.", nameof(signarue)); - } -#endif // LIBPLANET_DPOS -} +// throw new ArgumentException("Invalid signature.", nameof(signarue)); +// } +// #endif // LIBPLANET_DPOS +// } diff --git a/src/node/LibplanetConsole.Node.Example/ExampleService.cs b/src/node/LibplanetConsole.Node.Example/ExampleService.cs index d8920239..67533de3 100644 --- a/src/node/LibplanetConsole.Node.Example/ExampleService.cs +++ b/src/node/LibplanetConsole.Node.Example/ExampleService.cs @@ -1,43 +1,42 @@ -using LibplanetConsole.Common; -using LibplanetConsole.Common.Services; -using LibplanetConsole.Example.Services; +// using LibplanetConsole.Common; +// using LibplanetConsole.Example.Services; -namespace LibplanetConsole.Node.Example; +// namespace LibplanetConsole.Node.Example; -internal sealed class ExampleService : LocalService, - IExampleNodeService, IDisposable -{ - private readonly Example _example; +// internal sealed class ExampleService : LocalService, +// IExampleNodeService, IDisposable +// { +// private readonly Example _example; - public ExampleService(Example example) - { - _example = example; - _example.Subscribed += Example_Subscribed; - _example.Unsubscribed += Example_Unsubscribed; - } +// public ExampleService(Example example) +// { +// _example = example; +// _example.Subscribed += Example_Subscribed; +// _example.Unsubscribed += Example_Unsubscribed; +// } - public void Dispose() - { - _example.Subscribed -= Example_Subscribed; - _example.Unsubscribed -= Example_Unsubscribed; - } +// public void Dispose() +// { +// _example.Subscribed -= Example_Subscribed; +// _example.Unsubscribed -= Example_Unsubscribed; +// } - public async Task GetAddressCountAsync(CancellationToken cancellationToken) - { - var addresses = await _example.GetAddressesAsync(cancellationToken); - return addresses.Length; - } +// public async Task GetAddressCountAsync(CancellationToken cancellationToken) +// { +// var addresses = await _example.GetAddressesAsync(cancellationToken); +// return addresses.Length; +// } - public Task GetAddressesAsync(CancellationToken cancellationToken) - => _example.GetAddressesAsync(cancellationToken); +// public Task GetAddressesAsync(CancellationToken cancellationToken) +// => _example.GetAddressesAsync(cancellationToken); - public void Subscribe(Address address) => _example.Subscribe(address); +// public void Subscribe(Address address) => _example.Subscribe(address); - public void Unsubscribe(Address address) => _example.Unsubscribe(address); +// public void Unsubscribe(Address address) => _example.Unsubscribe(address); - private void Example_Subscribed(object? sender, ItemEventArgs
e) - => Callback.OnSubscribed(e.Item); +// private void Example_Subscribed(object? sender, ItemEventArgs
e) +// => Callback.OnSubscribed(e.Item); - private void Example_Unsubscribed(object? sender, ItemEventArgs
e) - => Callback.OnUnsubscribed(e.Item); -} +// private void Example_Unsubscribed(object? sender, ItemEventArgs
e) +// => Callback.OnUnsubscribed(e.Item); +// } diff --git a/src/node/LibplanetConsole.Node.Example/ServiceCollectionExtensions.cs b/src/node/LibplanetConsole.Node.Example/ServiceCollectionExtensions.cs index bedecab6..eb0791bd 100644 --- a/src/node/LibplanetConsole.Node.Example/ServiceCollectionExtensions.cs +++ b/src/node/LibplanetConsole.Node.Example/ServiceCollectionExtensions.cs @@ -1,6 +1,5 @@ using JSSoft.Commands; using LibplanetConsole.Common; -using LibplanetConsole.Common.Services; using LibplanetConsole.Node.Example.Commands; using Microsoft.Extensions.DependencyInjection; @@ -13,7 +12,7 @@ public static IServiceCollection AddExample(this IServiceCollection @this) @this.AddSingleton() .AddSingleton(s => s.GetRequiredService()); @this.AddSingleton(); - @this.AddSingleton(); + // @this.AddSingleton(); @this.AddSingleton(); return @this; } diff --git a/src/node/LibplanetConsole.Node.Explorer/ServiceCollectionExtensions.cs b/src/node/LibplanetConsole.Node.Explorer/ServiceCollectionExtensions.cs index 0425eb39..a4b9f453 100644 --- a/src/node/LibplanetConsole.Node.Explorer/ServiceCollectionExtensions.cs +++ b/src/node/LibplanetConsole.Node.Explorer/ServiceCollectionExtensions.cs @@ -1,9 +1,7 @@ using JSSoft.Commands; using LibplanetConsole.Common; -using LibplanetConsole.Common.Services; using LibplanetConsole.Framework; using LibplanetConsole.Node.Explorer.Commands; -using LibplanetConsole.Node.Explorer.Services; using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Node.Explorer; @@ -16,7 +14,7 @@ public static IServiceCollection AddExplorer(this IServiceCollection @this) .AddSingleton(s => s.GetRequiredService()) .AddSingleton(s => s.GetRequiredService()); @this.AddSingleton(); - @this.AddSingleton(); + // @this.AddSingleton(); @this.AddSingleton(); return @this; } diff --git a/src/node/LibplanetConsole.Node.Explorer/Services/ExplorerService.cs b/src/node/LibplanetConsole.Node.Explorer/Services/ExplorerService.cs index a78ddd44..254ccaf0 100644 --- a/src/node/LibplanetConsole.Node.Explorer/Services/ExplorerService.cs +++ b/src/node/LibplanetConsole.Node.Explorer/Services/ExplorerService.cs @@ -3,14 +3,14 @@ using LibplanetConsole.Explorer.Services; using Microsoft.Extensions.Logging; -namespace LibplanetConsole.Node.Explorer.Services; +// namespace LibplanetConsole.Node.Explorer.Services; -internal sealed class ExplorerService : LocalService, - IExplorerService, IDisposable -{ - private readonly INode _node; - private readonly Explorer _explorer; - private readonly ILogger _logger; +// internal sealed class ExplorerService : LocalService, +// IExplorerService, IDisposable +// { +// private readonly INode _node; +// private readonly Explorer _explorer; +// private readonly ILogger _logger; public ExplorerService(INode node, Explorer explorer, ILogger logger) { @@ -29,19 +29,19 @@ public void Dispose() _logger.LogDebug("ExplorerService is disposed: {NodeAddress}", _node.Address); } - public Task GetInfoAsync(CancellationToken cancellationToken) - => Task.Run(() => _explorer.Info, cancellationToken); +// public Task GetInfoAsync(CancellationToken cancellationToken) +// => Task.Run(() => _explorer.Info, cancellationToken); - public Task StartAsync( - ExplorerOptions options, CancellationToken cancellationToken) - => _explorer.StartAsync(options, cancellationToken); +// public Task StartAsync( +// ExplorerOptions options, CancellationToken cancellationToken) +// => _explorer.StartAsync(options, cancellationToken); - public Task StopAsync(CancellationToken cancellationToken) - => _explorer.StopAsync(cancellationToken); +// public Task StopAsync(CancellationToken cancellationToken) +// => _explorer.StopAsync(cancellationToken); - private void ExplorerNode_Started(object? sender, EventArgs e) - => Callback.OnStarted(_explorer.Info); +// private void ExplorerNode_Started(object? sender, EventArgs e) +// => Callback.OnStarted(_explorer.Info); - private void ExplorerNode_Stopped(object? sender, EventArgs e) - => Callback.OnStopped(); -} +// private void ExplorerNode_Stopped(object? sender, EventArgs e) +// => Callback.OnStopped(); +// } diff --git a/src/node/LibplanetConsole.Node/ApplicationBase.cs b/src/node/LibplanetConsole.Node/ApplicationBase.cs index 61edec6d..5292a919 100644 --- a/src/node/LibplanetConsole.Node/ApplicationBase.cs +++ b/src/node/LibplanetConsole.Node/ApplicationBase.cs @@ -15,7 +15,7 @@ public abstract class ApplicationBase : ApplicationFramework, IApplication private readonly ILogger _logger; private readonly ApplicationInfo _info; private readonly Task _waitForExitTask = Task.CompletedTask; - private NodeContext? _nodeContext; + // private NodeContext? _nodeContext; private Guid _closeToken; protected ApplicationBase(IServiceProvider serviceProvider, ApplicationOptions options) diff --git a/src/node/LibplanetConsole.Node/LibplanetConsole.Node.csproj b/src/node/LibplanetConsole.Node/LibplanetConsole.Node.csproj index d657231a..7644f857 100644 --- a/src/node/LibplanetConsole.Node/LibplanetConsole.Node.csproj +++ b/src/node/LibplanetConsole.Node/LibplanetConsole.Node.csproj @@ -20,7 +20,10 @@ + + + diff --git a/src/node/LibplanetConsole.Node/Node.cs b/src/node/LibplanetConsole.Node/Node.cs index 4e5cd0b3..98a07ace 100644 --- a/src/node/LibplanetConsole.Node/Node.cs +++ b/src/node/LibplanetConsole.Node/Node.cs @@ -10,7 +10,6 @@ using LibplanetConsole.Common; using LibplanetConsole.Common.Exceptions; using LibplanetConsole.Common.Extensions; -using LibplanetConsole.Common.Services; using LibplanetConsole.Seed; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -297,34 +296,35 @@ private static async Task CreateTransport( private static async Task GetSeedInfoAsync( EndPoint seedEndPoint, CancellationToken cancellationToken) { - var remoteService = new RemoteService(); - var remoteServiceContext = new RemoteServiceContext([remoteService]) - { - EndPoint = seedEndPoint, - }; - var closeToken = await remoteServiceContext.OpenAsync(cancellationToken); - var service = remoteService.Service; - var privateKey = new PrivateKey(); - var publicKey = privateKey.PublicKey; - try - { - for (var i = 0; i < 10; i++) - { - var seedInfo = await service.GetSeedAsync(publicKey, cancellationToken); - if (Equals(seedInfo, SeedInfo.Empty) != true) - { - return seedInfo; - } - - await Task.Delay(500, cancellationToken); - } - - throw new InvalidOperationException("No seed information is available."); - } - finally - { - await remoteServiceContext.CloseAsync(closeToken, cancellationToken); - } + // var remoteService = new RemoteService(); + // var remoteServiceContext = new RemoteServiceContext([remoteService]) + // { + // EndPoint = seedEndPoint, + // }; + // var closeToken = await remoteServiceContext.OpenAsync(cancellationToken); + // var service = remoteService.Service; + // var privateKey = new PrivateKey(); + // var publicKey = privateKey.PublicKey; + // try + // { + // for (var i = 0; i < 10; i++) + // { + // var seedInfo = await service.GetSeedAsync(publicKey, cancellationToken); + // if (Equals(seedInfo, SeedInfo.Empty) != true) + // { + // return seedInfo; + // } + + // await Task.Delay(500, cancellationToken); + // } + + // throw new InvalidOperationException("No seed information is available."); + // } + // finally + // { + // await remoteServiceContext.CloseAsync(closeToken, cancellationToken); + // } + throw new NotImplementedException(); } private void UpdateNodeInfo() diff --git a/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs b/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs index 981c1547..a19c5e41 100644 --- a/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs +++ b/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs @@ -1,6 +1,5 @@ using JSSoft.Commands; using LibplanetConsole.Common; -using LibplanetConsole.Common.Services; using LibplanetConsole.Framework; using LibplanetConsole.Node.Commands; using LibplanetConsole.Node.Services; @@ -16,12 +15,12 @@ public static IServiceCollection AddNode( @this.AddSingleton(s => new Node(s, options)) .AddSingleton(s => s.GetRequiredService()) .AddSingleton(s => s.GetRequiredService()); - @this.AddSingleton(); - @this.AddSingleton() - .AddSingleton(s => s.GetRequiredService()) - .AddSingleton(s => s.GetRequiredService()); - @this.AddSingleton(); - @this.AddSingleton(); + // @this.AddSingleton(); + // @this.AddSingleton() + // .AddSingleton(s => s.GetRequiredService()) + // .AddSingleton(s => s.GetRequiredService()); + // @this.AddSingleton(); + // @this.AddSingleton(); @this.AddSingleton(); @this.AddSingleton(); diff --git a/src/node/LibplanetConsole.Node/Services/BlockChainService.cs b/src/node/LibplanetConsole.Node/Services/BlockChainService.cs index 79316674..5d3d1fcc 100644 --- a/src/node/LibplanetConsole.Node/Services/BlockChainService.cs +++ b/src/node/LibplanetConsole.Node/Services/BlockChainService.cs @@ -4,69 +4,69 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace LibplanetConsole.Node.Services; +// namespace LibplanetConsole.Node.Services; -internal sealed class BlockChainService - : LocalService, IBlockChainService -{ - private static readonly Codec _codec = new(); - private readonly ILogger _logger; - private readonly Node _node; +// internal sealed class BlockChainService +// : LocalService, IBlockChainService +// { +// private static readonly Codec _codec = new(); +// private readonly ILogger _logger; +// private readonly Node _node; - public BlockChainService(Node node) - { - _node = node; - _logger = node.GetLogger(); - _node.BlockAppended += Node_BlockAppended; - } +// public BlockChainService(Node node) +// { +// _node = node; +// _logger = node.GetLogger(); +// _node.BlockAppended += Node_BlockAppended; +// } - public async Task SendTransactionAsync( - byte[] transaction, CancellationToken cancellationToken) - { - var tx = Transaction.Deserialize(transaction); - await _node.AddTransactionAsync(tx, cancellationToken); - return tx.Id; - } +// public async Task SendTransactionAsync( +// byte[] transaction, CancellationToken cancellationToken) +// { +// var tx = Transaction.Deserialize(transaction); +// await _node.AddTransactionAsync(tx, cancellationToken); +// return tx.Id; +// } - public Task GetNextNonceAsync(Address address, CancellationToken cancellationToken) - => _node.GetNextNonceAsync(address, cancellationToken); +// public Task GetNextNonceAsync(Address address, CancellationToken cancellationToken) +// => _node.GetNextNonceAsync(address, cancellationToken); - public Task GetTipHashAsync(CancellationToken cancellationToken) - => _node.GetTipHashAsync(cancellationToken); +// public Task GetTipHashAsync(CancellationToken cancellationToken) +// => _node.GetTipHashAsync(cancellationToken); - public async Task GetStateAsync( - BlockHash? blockHash, - Address accountAddress, - Address address, - CancellationToken cancellationToken) - { - var value = await _node.GetStateAsync( - blockHash, accountAddress, address, cancellationToken); - return _codec.Encode(value); - } +// public async Task GetStateAsync( +// BlockHash? blockHash, +// Address accountAddress, +// Address address, +// CancellationToken cancellationToken) +// { +// var value = await _node.GetStateAsync( +// blockHash, accountAddress, address, cancellationToken); +// return _codec.Encode(value); +// } - public async Task GetStateByStateRootHashAsync( - HashDigest stateRootHash, - Address accountAddress, - Address address, - CancellationToken cancellationToken) - { - var value = await _node.GetStateByStateRootHashAsync( - stateRootHash, accountAddress, address, cancellationToken); - return _codec.Encode(value); - } +// public async Task GetStateByStateRootHashAsync( +// HashDigest stateRootHash, +// Address accountAddress, +// Address address, +// CancellationToken cancellationToken) +// { +// var value = await _node.GetStateByStateRootHashAsync( +// stateRootHash, accountAddress, address, cancellationToken); +// return _codec.Encode(value); +// } - public Task GetBlockHashAsync(long height, CancellationToken cancellationToken) - => _node.GetBlockHashAsync(height, cancellationToken); +// public Task GetBlockHashAsync(long height, CancellationToken cancellationToken) +// => _node.GetBlockHashAsync(height, cancellationToken); - public Task GetActionAsync( - TxId txId, int actionIndex, CancellationToken cancellationToken) - => _node.GetActionAsync(txId, actionIndex, cancellationToken); +// public Task GetActionAsync( +// TxId txId, int actionIndex, CancellationToken cancellationToken) +// => _node.GetActionAsync(txId, actionIndex, cancellationToken); - private void Node_BlockAppended(object? sender, BlockEventArgs e) - { - var blockInfo = e.BlockInfo; - Callback.OnBlockAppended(blockInfo); - _logger.LogDebug("Callback.OnBlockAppended: {BlockHash}", blockInfo.Hash); - } -} +// private void Node_BlockAppended(object? sender, BlockEventArgs e) +// { +// var blockInfo = e.BlockInfo; +// Callback.OnBlockAppended(blockInfo); +// _logger.LogDebug("Callback.OnBlockAppended: {BlockHash}", blockInfo.Hash); +// } +//} diff --git a/src/node/LibplanetConsole.Node/Services/NodeContext.cs b/src/node/LibplanetConsole.Node/Services/NodeContext.cs index e94edcc7..c9355c47 100644 --- a/src/node/LibplanetConsole.Node/Services/NodeContext.cs +++ b/src/node/LibplanetConsole.Node/Services/NodeContext.cs @@ -1,8 +1,6 @@ -using LibplanetConsole.Common.Services; +// namespace LibplanetConsole.Node.Services; -namespace LibplanetConsole.Node.Services; - -internal sealed class NodeContext(IEnumerable localServices) - : LocalServiceContext(localServices) -{ -} +// internal sealed class NodeContext(IEnumerable localServices) +// : LocalServiceContext(localServices) +// { +// } diff --git a/src/node/LibplanetConsole.Node/Services/NodeService.cs b/src/node/LibplanetConsole.Node/Services/NodeService.cs index 82aa992a..24f33692 100644 --- a/src/node/LibplanetConsole.Node/Services/NodeService.cs +++ b/src/node/LibplanetConsole.Node/Services/NodeService.cs @@ -2,12 +2,12 @@ using LibplanetConsole.Common.Services; using Microsoft.Extensions.Logging; -namespace LibplanetConsole.Node.Services; +// namespace LibplanetConsole.Node.Services; -internal sealed class NodeService : LocalService, INodeService -{ - private readonly Node _node; - private readonly ILogger _logger; +// internal sealed class NodeService : LocalService, INodeService +// { +// private readonly Node _node; +// private readonly ILogger _logger; public NodeService(Node node, ILogger logger) { @@ -17,11 +17,11 @@ public NodeService(Node node, ILogger logger) _node.Stopped += (s, e) => Callback.OnStopped(); } - public async Task GetInfoAsync(CancellationToken cancellationToken) - { - await Task.CompletedTask; - return _node.Info; - } +// public async Task GetInfoAsync(CancellationToken cancellationToken) +// { +// await Task.CompletedTask; +// return _node.Info; +// } public async Task StartAsync(string seedEndPoint, CancellationToken cancellationToken) { @@ -31,6 +31,6 @@ public async Task StartAsync(string seedEndPoint, CancellationToken ca return _node.Info; } - public Task StopAsync(CancellationToken cancellationToken) - => _node.StopAsync(cancellationToken); -} +// public Task StopAsync(CancellationToken cancellationToken) +// => _node.StopAsync(cancellationToken); +// } diff --git a/src/node/LibplanetConsole.Node/Services/SeedService.cs b/src/node/LibplanetConsole.Node/Services/SeedService.cs index b0b27ea4..3134362d 100644 --- a/src/node/LibplanetConsole.Node/Services/SeedService.cs +++ b/src/node/LibplanetConsole.Node/Services/SeedService.cs @@ -1,68 +1,67 @@ -using LibplanetConsole.Common.Services; -using LibplanetConsole.Framework; -using LibplanetConsole.Seed; -using static LibplanetConsole.Common.EndPointUtility; +// using LibplanetConsole.Framework; +// using LibplanetConsole.Seed; +// using static LibplanetConsole.Common.EndPointUtility; -namespace LibplanetConsole.Node.Services; +// namespace LibplanetConsole.Node.Services; -internal sealed class SeedService(IApplication application) - : LocalService, ISeedService, IApplicationService, IAsyncDisposable -{ - private readonly PrivateKey _seedNodePrivateKey = new(); - private SeedNode? _blocksyncSeedNode; - private SeedNode? _consensusSeedNode; +// internal sealed class SeedService(IApplication application) +// : LocalService, ISeedService, IApplicationService, IAsyncDisposable +// { +// private readonly PrivateKey _seedNodePrivateKey = new(); +// private SeedNode? _blocksyncSeedNode; +// private SeedNode? _consensusSeedNode; - public Task GetSeedAsync( - PublicKey publicKey, CancellationToken cancellationToken) - { - if (_blocksyncSeedNode is null || _consensusSeedNode is null) - { - throw new InvalidOperationException("The SeedService is not running."); - } +// public Task GetSeedAsync( +// PublicKey publicKey, CancellationToken cancellationToken) +// { +// if (_blocksyncSeedNode is null || _consensusSeedNode is null) +// { +// throw new InvalidOperationException("The SeedService is not running."); +// } - var seedPeer = _blocksyncSeedNode.BoundPeer; - var consensusSeedPeer = _consensusSeedNode.BoundPeer; - var seedInfo = new SeedInfo - { - BlocksyncSeedPeer = seedPeer, - ConsensusSeedPeer = consensusSeedPeer, - }; +// var seedPeer = _blocksyncSeedNode.BoundPeer; +// var consensusSeedPeer = _consensusSeedNode.BoundPeer; +// var seedInfo = new SeedInfo +// { +// BlocksyncSeedPeer = seedPeer, +// ConsensusSeedPeer = consensusSeedPeer, +// }; - return Task.Run(() => seedInfo, cancellationToken); - } +// return Task.Run(() => seedInfo, cancellationToken); +// } - async ValueTask IAsyncDisposable.DisposeAsync() - { - if (_blocksyncSeedNode is not null) - { - await _blocksyncSeedNode.StopAsync(cancellationToken: default); - _blocksyncSeedNode = null; - } +// async ValueTask IAsyncDisposable.DisposeAsync() +// { +// if (_blocksyncSeedNode is not null) +// { +// await _blocksyncSeedNode.StopAsync(cancellationToken: default); +// _blocksyncSeedNode = null; +// } - if (_consensusSeedNode is not null) - { - await _consensusSeedNode.StopAsync(cancellationToken: default); - _consensusSeedNode = null; - } - } +// if (_consensusSeedNode is not null) +// { +// await _consensusSeedNode.StopAsync(cancellationToken: default); +// _consensusSeedNode = null; +// } +// } - async Task IApplicationService.InitializeAsync(CancellationToken cancellationToken) - { - var info = application.Info; - if (CompareEndPoint(info.SeedEndPoint, info.EndPoint) is true) - { - _blocksyncSeedNode = new SeedNode(new() - { - PrivateKey = _seedNodePrivateKey, - EndPoint = NextEndPoint(), - }); - _consensusSeedNode = new SeedNode(new() - { - PrivateKey = _seedNodePrivateKey, - EndPoint = NextEndPoint(), - }); - await _blocksyncSeedNode.StartAsync(cancellationToken); - await _consensusSeedNode.StartAsync(cancellationToken); - } - } -} +// async Task IApplicationService.InitializeAsync(CancellationToken cancellationToken) +// { +// var info = application.Info; +// if (CompareEndPoint(info.SeedEndPoint, info.EndPoint) is true) +// { +// _blocksyncSeedNode = new SeedNode(new() +// { +// PrivateKey = _seedNodePrivateKey, +// EndPoint = NextEndPoint(), +// }); +// _consensusSeedNode = new SeedNode(new() +// { +// PrivateKey = _seedNodePrivateKey, +// EndPoint = NextEndPoint(), +// }); +// await _blocksyncSeedNode.StartAsync(cancellationToken); +// await _consensusSeedNode.StartAsync(cancellationToken); +// } +// } +// } diff --git a/src/shared/LibplanetConsole.Node/Services/NodeService.proto b/src/shared/LibplanetConsole.Node/Services/NodeService.proto new file mode 100644 index 00000000..48e40d6f --- /dev/null +++ b/src/shared/LibplanetConsole.Node/Services/NodeService.proto @@ -0,0 +1,44 @@ +syntax = "proto3"; + +option csharp_namespace = "Libplanet.Node.API"; + +package libplanet.console.node.v1; + +service Node { + rpc GetGenesisBlock (GetGenesisBlockRequest) returns (GetGenesisBlockReply); + rpc GetTip(Empty) returns (GetTipReply); + rpc GetBlock(GetBlockRequest) returns (GetBlockReply); +} + +message Empty { +} + +message GetGenesisBlockRequest { +} + +message GetGenesisBlockReply { + string hash = 1; +} + +message GetTipReply { + string hash = 1; + int64 height = 2; +} + +message GetBlockRequest { + oneof block_identifier { + int64 height = 1; + string hash = 2; + } +} + +message GetBlockReply { + string hash = 1; + int64 height = 2; + string miner = 3; + string public_key = 4; + string previous_hash = 5; + string state_root_hash = 6; + string signature = 7; + int64 protocol_version = 8; +} \ No newline at end of file From e326280764fdf1ac916529567684a1c3bf27fcf2 Mon Sep 17 00:00:00 2001 From: s2quake Date: Sat, 5 Oct 2024 18:29:00 +0900 Subject: [PATCH 02/18] refactor: Successful node run --- libplanet-console.sln | 7 - .../ApplicationBase.cs | 20 +-- .../Client.BlockChain.cs | 25 ++-- src/client/LibplanetConsole.Client/Client.cs | 18 ++- .../LibplanetConsole.Client.csproj | 6 + .../LibplanetConsole.Seed.csproj | 10 +- .../Protos/SeedGrpcService.proto | 22 +++ src/common/LibplanetConsole.Seed/SeedInfo.cs | 18 +++ .../ServiceCollectionExtensions.cs | 2 - .../Commands/ExplorerCommand.cs | 43 ------ .../Explorer.cs | 129 ------------------ .../ExplorerInfoProvider.cs | 21 --- .../ExplorerSettings.cs | 12 -- .../IExplorer.cs | 20 --- .../LibplanetConsole.Console.Explorer.csproj | 11 -- .../ServiceCollectionExtensions.cs | 23 ---- .../ApplicationBase.cs | 20 +-- .../LibplanetConsole.Console/Client.cs | 2 +- .../LibplanetConsole.Console.csproj | 5 + src/console/LibplanetConsole.Console/Node.cs | 4 +- .../EntryCommands/StartCommand.cs | 40 +++++- .../ServiceCollectionExtensions.cs | 3 +- .../Commands/ExplorerCommand.cs | 30 ---- .../Explorer.cs | 77 ----------- .../ExplorerInfoProvider.cs | 9 -- .../ExplorerSettings.cs | 27 ---- .../IExplorer.cs | 18 --- .../LibplanetConsole.Node.Explorer.csproj | 5 + .../NodeEndpointRouteBuilderExtensions.cs | 19 +++ .../ServiceCollectionExtensions.cs | 21 ++- .../Services/ExplorerService.cs | 47 ------- .../LibplanetConsole.Node/ApplicationBase.cs | 32 ++--- .../LibplanetConsole.Node.csproj | 2 +- src/node/LibplanetConsole.Node/Node.cs | 45 ++---- .../NodeEndpointRouteBuilderExtensions.cs | 17 +++ .../NodeHostedService.cs | 33 +++++ src/node/LibplanetConsole.Node/SeedService.cs | 66 +++++++++ .../ServiceCollectionExtensions.cs | 9 +- .../Services/BlockChainGrpcServiceV1.cs | 63 +++++++++ .../Services/BlockChainService.cs | 72 ---------- .../Services/NodeContext.cs | 6 - .../Services/NodeGrpcServiceV1.cs | 29 ++++ .../Services/NodeService.cs | 36 ----- .../Services/SeedGrpcServiceV1.cs | 20 +++ .../Services/SeedService.cs | 67 --------- .../LibplanetConsole.Explorer/ExplorerInfo.cs | 14 -- .../ExplorerOptions.cs | 10 -- .../Services/IExplorerCallback.cs | 8 -- .../Services/IExplorerService.cs | 10 -- src/shared/LibplanetConsole.Node/NodeInfo.cs | 32 +++++ .../Protos/BlockChainGrpcService.proto | 64 +++++++++ .../Protos/NodeGrpcService.proto | 43 ++++++ .../Services/NodeService.proto | 44 ------ 53 files changed, 579 insertions(+), 857 deletions(-) create mode 100644 src/common/LibplanetConsole.Seed/Protos/SeedGrpcService.proto delete mode 100644 src/console/LibplanetConsole.Console.Explorer/Commands/ExplorerCommand.cs delete mode 100644 src/console/LibplanetConsole.Console.Explorer/Explorer.cs delete mode 100644 src/console/LibplanetConsole.Console.Explorer/ExplorerInfoProvider.cs delete mode 100644 src/console/LibplanetConsole.Console.Explorer/ExplorerSettings.cs delete mode 100644 src/console/LibplanetConsole.Console.Explorer/IExplorer.cs delete mode 100644 src/console/LibplanetConsole.Console.Explorer/LibplanetConsole.Console.Explorer.csproj delete mode 100644 src/console/LibplanetConsole.Console.Explorer/ServiceCollectionExtensions.cs delete mode 100644 src/node/LibplanetConsole.Node.Explorer/Commands/ExplorerCommand.cs delete mode 100644 src/node/LibplanetConsole.Node.Explorer/Explorer.cs delete mode 100644 src/node/LibplanetConsole.Node.Explorer/ExplorerInfoProvider.cs delete mode 100644 src/node/LibplanetConsole.Node.Explorer/ExplorerSettings.cs delete mode 100644 src/node/LibplanetConsole.Node.Explorer/IExplorer.cs create mode 100644 src/node/LibplanetConsole.Node.Explorer/NodeEndpointRouteBuilderExtensions.cs delete mode 100644 src/node/LibplanetConsole.Node.Explorer/Services/ExplorerService.cs create mode 100644 src/node/LibplanetConsole.Node/NodeEndpointRouteBuilderExtensions.cs create mode 100644 src/node/LibplanetConsole.Node/NodeHostedService.cs create mode 100644 src/node/LibplanetConsole.Node/SeedService.cs create mode 100644 src/node/LibplanetConsole.Node/Services/BlockChainGrpcServiceV1.cs delete mode 100644 src/node/LibplanetConsole.Node/Services/BlockChainService.cs delete mode 100644 src/node/LibplanetConsole.Node/Services/NodeContext.cs create mode 100644 src/node/LibplanetConsole.Node/Services/NodeGrpcServiceV1.cs delete mode 100644 src/node/LibplanetConsole.Node/Services/NodeService.cs create mode 100644 src/node/LibplanetConsole.Node/Services/SeedGrpcServiceV1.cs delete mode 100644 src/node/LibplanetConsole.Node/Services/SeedService.cs delete mode 100644 src/shared/LibplanetConsole.Explorer/ExplorerInfo.cs delete mode 100644 src/shared/LibplanetConsole.Explorer/ExplorerOptions.cs delete mode 100644 src/shared/LibplanetConsole.Explorer/Services/IExplorerCallback.cs delete mode 100644 src/shared/LibplanetConsole.Explorer/Services/IExplorerService.cs create mode 100644 src/shared/LibplanetConsole.Node/Protos/BlockChainGrpcService.proto create mode 100644 src/shared/LibplanetConsole.Node/Protos/NodeGrpcService.proto delete mode 100644 src/shared/LibplanetConsole.Node/Services/NodeService.proto diff --git a/libplanet-console.sln b/libplanet-console.sln index 09662b3b..f2d86b23 100644 --- a/libplanet-console.sln +++ b/libplanet-console.sln @@ -43,8 +43,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibplanetConsole.Console.Ex EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibplanetConsole.Node.Explorer", "src\node\LibplanetConsole.Node.Explorer\LibplanetConsole.Node.Explorer.csproj", "{FEF8E9D4-CBB7-4EFC-A5C2-2C9E91498D79}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibplanetConsole.Console.Explorer", "src\console\LibplanetConsole.Console.Explorer\LibplanetConsole.Console.Explorer.csproj", "{AF1A0011-2795-42FD-B67B-7F1956268577}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibplanetConsole.Common.Tests", "test\LibplanetConsole.Common.Tests\LibplanetConsole.Common.Tests.csproj", "{65341396-A058-4577-9B70-C1DD3D146501}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibplanetConsole.Node.Evidence", "src\node\LibplanetConsole.Node.Evidence\LibplanetConsole.Node.Evidence.csproj", "{4C151EAE-2105-4DA1-B645-C09513EA8532}" @@ -119,10 +117,6 @@ Global {FEF8E9D4-CBB7-4EFC-A5C2-2C9E91498D79}.Debug|Any CPU.Build.0 = Debug|Any CPU {FEF8E9D4-CBB7-4EFC-A5C2-2C9E91498D79}.Release|Any CPU.ActiveCfg = Release|Any CPU {FEF8E9D4-CBB7-4EFC-A5C2-2C9E91498D79}.Release|Any CPU.Build.0 = Release|Any CPU - {AF1A0011-2795-42FD-B67B-7F1956268577}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AF1A0011-2795-42FD-B67B-7F1956268577}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AF1A0011-2795-42FD-B67B-7F1956268577}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AF1A0011-2795-42FD-B67B-7F1956268577}.Release|Any CPU.Build.0 = Release|Any CPU {65341396-A058-4577-9B70-C1DD3D146501}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {65341396-A058-4577-9B70-C1DD3D146501}.Debug|Any CPU.Build.0 = Debug|Any CPU {65341396-A058-4577-9B70-C1DD3D146501}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -170,7 +164,6 @@ Global {2CA203CA-1980-4B11-82E1-0E5812D86C63} = {1DEAA4CE-E29B-4379-BAF6-20B79A5946CB} {ACE75CD7-E4FD-4B9E-943E-EF3BB11DECDF} = {CAB76DA9-6E57-4422-98C6-DD2D6299F675} {FEF8E9D4-CBB7-4EFC-A5C2-2C9E91498D79} = {4A8F8EE9-769C-4C97-89BC-19D038E69998} - {AF1A0011-2795-42FD-B67B-7F1956268577} = {CAB76DA9-6E57-4422-98C6-DD2D6299F675} {65341396-A058-4577-9B70-C1DD3D146501} = {56942891-CFBD-41E4-8881-47F455D7BEFD} {4C151EAE-2105-4DA1-B645-C09513EA8532} = {4A8F8EE9-769C-4C97-89BC-19D038E69998} {965DF40E-F3BA-45F2-A177-A3DE550C824E} = {CAB76DA9-6E57-4422-98C6-DD2D6299F675} diff --git a/src/client/LibplanetConsole.Client/ApplicationBase.cs b/src/client/LibplanetConsole.Client/ApplicationBase.cs index 99d57d77..edbf1ee8 100644 --- a/src/client/LibplanetConsole.Client/ApplicationBase.cs +++ b/src/client/LibplanetConsole.Client/ApplicationBase.cs @@ -54,9 +54,9 @@ protected ApplicationBase(IServiceProvider serviceProvider, ApplicationOptions o protected override async Task OnRunAsync(CancellationToken cancellationToken) { _logger.LogDebug("ClientServiceContext is starting: {EndPoint}", _info.EndPoint); - _clientServiceContext = _serviceProvider.GetRequiredService(); - _clientServiceContext.EndPoint = _info.EndPoint; - _closeToken = await _clientServiceContext.StartAsync(cancellationToken: default); + // _clientServiceContext = _serviceProvider.GetRequiredService(); + // _clientServiceContext.EndPoint = _info.EndPoint; + // _closeToken = await _clientServiceContext.StartAsync(cancellationToken: default); _logger.LogDebug("ClientServiceContext is started: {EndPoint}", _info.EndPoint); await base.OnRunAsync(cancellationToken); await AutoStartAsync(cancellationToken); @@ -65,13 +65,13 @@ protected override async Task OnRunAsync(CancellationToken cancellationToken) protected override async ValueTask OnDisposeAsync() { await base.OnDisposeAsync(); - if (_clientServiceContext is not null) - { - _logger.LogDebug("ClientServiceContext is closing: {EndPoint}", _info.EndPoint); - await _clientServiceContext.CloseAsync(_closeToken, CancellationToken.None); - _clientServiceContext = null; - _logger.LogDebug("ClientServiceContext is closed: {EndPoint}", _info.EndPoint); - } + // if (_clientServiceContext is not null) + // { + // _logger.LogDebug("ClientServiceContext is closing: {EndPoint}", _info.EndPoint); + // await _clientServiceContext.CloseAsync(_closeToken, CancellationToken.None); + // _clientServiceContext = null; + // _logger.LogDebug("ClientServiceContext is closed: {EndPoint}", _info.EndPoint); + // } await _waitForExitTask; } diff --git a/src/client/LibplanetConsole.Client/Client.BlockChain.cs b/src/client/LibplanetConsole.Client/Client.BlockChain.cs index 40aaaad6..3d4c2fa7 100644 --- a/src/client/LibplanetConsole.Client/Client.BlockChain.cs +++ b/src/client/LibplanetConsole.Client/Client.BlockChain.cs @@ -12,18 +12,19 @@ internal sealed partial class Client : IBlockChain public async Task SendTransactionAsync( IAction[] actions, CancellationToken cancellationToken) { - var privateKey = PrivateKeyUtility.FromSecureString(_privateKey); - var address = privateKey.Address; - var nonce = await RemoteBlockChainService.GetNextNonceAsync(address, cancellationToken); - var genesisHash = NodeInfo.GenesisHash; - var tx = Transaction.Create( - nonce: nonce, - privateKey: privateKey, - genesisHash: genesisHash, - actions: [.. actions.Select(item => item.PlainValue)]); - var txData = tx.Serialize(); - _logger.LogDebug("Client sends a transaction: {TxId}", tx.Id); - return await RemoteBlockChainService.SendTransactionAsync(txData, cancellationToken); + // var privateKey = PrivateKeyUtility.FromSecureString(_privateKey); + // var address = privateKey.Address; + // var nonce = await RemoteBlockChainService.GetNextNonceAsync(address, cancellationToken); + // var genesisHash = NodeInfo.GenesisHash; + // var tx = Transaction.Create( + // nonce: nonce, + // privateKey: privateKey, + // genesisHash: genesisHash, + // actions: [.. actions.Select(item => item.PlainValue)]); + // var txData = tx.Serialize(); + // _logger.LogDebug("Client sends a transaction: {TxId}", tx.Id); + // return await RemoteBlockChainService.SendTransactionAsync(txData, cancellationToken); + throw new NotImplementedException(); } public Task GetBlockHashAsync(long height, CancellationToken cancellationToken) diff --git a/src/client/LibplanetConsole.Client/Client.cs b/src/client/LibplanetConsole.Client/Client.cs index 5b8a3d01..42d31290 100644 --- a/src/client/LibplanetConsole.Client/Client.cs +++ b/src/client/LibplanetConsole.Client/Client.cs @@ -1,9 +1,9 @@ using System.Security; +using Grpc.Net.Client; using LibplanetConsole.Common; using LibplanetConsole.Common.Extensions; using LibplanetConsole.Node; using LibplanetConsole.Node.Services; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace LibplanetConsole.Client; @@ -15,6 +15,7 @@ internal sealed partial class Client : IClient, INodeCallback, IBlockChainCallba private readonly ILogger _logger; private EndPoint? _nodeEndPoint; // private RemoteNodeContext? _remoteNodeContext; + private Node.Grpc.NodeGrpcService.NodeGrpcServiceClient? _remoteNode; private Guid _closeToken; private ClientInfo _info; @@ -75,10 +76,17 @@ public EndPoint NodeEndPoint public async Task StartAsync(CancellationToken cancellationToken) { - // if (_remoteNodeContext is not null) - // { - // throw new InvalidOperationException("The client is already running."); - // } + if (_remoteNode is not null) + { + throw new InvalidOperationException("The client is already running."); + } + + var address = $"http://{EndPointUtility.ToString(_nodeEndPoint)}"; + var channelOptions = new GrpcChannelOptions + { + }; + using var channel = GrpcChannel.ForAddress(address, channelOptions); + _remoteNode = new Node.Grpc.NodeGrpcService.NodeGrpcServiceClient(channel); // _remoteNodeContext = _serviceProvider.GetRequiredService(); // _remoteNodeContext.EndPoint = NodeEndPoint; diff --git a/src/client/LibplanetConsole.Client/LibplanetConsole.Client.csproj b/src/client/LibplanetConsole.Client/LibplanetConsole.Client.csproj index a2ef3c0c..db74743d 100644 --- a/src/client/LibplanetConsole.Client/LibplanetConsole.Client.csproj +++ b/src/client/LibplanetConsole.Client/LibplanetConsole.Client.csproj @@ -7,10 +7,16 @@ + LIBPLANET_CLIENT + + + + + diff --git a/src/common/LibplanetConsole.Seed/LibplanetConsole.Seed.csproj b/src/common/LibplanetConsole.Seed/LibplanetConsole.Seed.csproj index 0a8eacfe..f140bfee 100644 --- a/src/common/LibplanetConsole.Seed/LibplanetConsole.Seed.csproj +++ b/src/common/LibplanetConsole.Seed/LibplanetConsole.Seed.csproj @@ -14,9 +14,17 @@ - + + + + + + + + + diff --git a/src/common/LibplanetConsole.Seed/Protos/SeedGrpcService.proto b/src/common/LibplanetConsole.Seed/Protos/SeedGrpcService.proto new file mode 100644 index 00000000..111447c7 --- /dev/null +++ b/src/common/LibplanetConsole.Seed/Protos/SeedGrpcService.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +option csharp_namespace = "LibplanetConsole.Seed.Grpc"; + +package libplanet.console.seed.v1; + +service SeedGrpcService { + rpc GetSeed(GetSeedRequest) returns (GetSeedResponse); +} + +message SeedInfo { + string blocksyncSeedPeer = 1; + string consensusSeedPeer = 2; +} + +message GetSeedRequest { + string publicKey = 1; +} + +message GetSeedResponse { + SeedInfo seedResult = 1; +} diff --git a/src/common/LibplanetConsole.Seed/SeedInfo.cs b/src/common/LibplanetConsole.Seed/SeedInfo.cs index e0678136..8a247fbb 100644 --- a/src/common/LibplanetConsole.Seed/SeedInfo.cs +++ b/src/common/LibplanetConsole.Seed/SeedInfo.cs @@ -12,4 +12,22 @@ public readonly record struct SeedInfo [JsonConverter(typeof(BoundPeerJsonConverter))] public BoundPeer ConsensusSeedPeer { get; init; } + + public static implicit operator Grpc.SeedInfo(SeedInfo seedInfo) + { + return new Grpc.SeedInfo + { + BlocksyncSeedPeer = BoundPeerUtility.ToString(seedInfo.BlocksyncSeedPeer), + ConsensusSeedPeer = BoundPeerUtility.ToString(seedInfo.ConsensusSeedPeer), + }; + } + + public static implicit operator SeedInfo(Grpc.SeedInfo seedInfo) + { + return new SeedInfo + { + BlocksyncSeedPeer = BoundPeerUtility.Parse(seedInfo.BlocksyncSeedPeer), + ConsensusSeedPeer = BoundPeerUtility.Parse(seedInfo.ConsensusSeedPeer), + }; + } } diff --git a/src/console/LibplanetConsole.Console.Executable/ServiceCollectionExtensions.cs b/src/console/LibplanetConsole.Console.Executable/ServiceCollectionExtensions.cs index f8ec0535..2a899e52 100644 --- a/src/console/LibplanetConsole.Console.Executable/ServiceCollectionExtensions.cs +++ b/src/console/LibplanetConsole.Console.Executable/ServiceCollectionExtensions.cs @@ -3,7 +3,6 @@ using LibplanetConsole.Console.Example; using LibplanetConsole.Console.Executable.Commands; using LibplanetConsole.Console.Executable.Tracers; -using LibplanetConsole.Console.Explorer; using LibplanetConsole.Framework; using LibplanetConsole.Logging; using Microsoft.Extensions.DependencyInjection; @@ -33,7 +32,6 @@ public static IServiceCollection AddApplication( @this.AddExample(); @this.AddEvidence(); - @this.AddExplorer(); return @this; } diff --git a/src/console/LibplanetConsole.Console.Explorer/Commands/ExplorerCommand.cs b/src/console/LibplanetConsole.Console.Explorer/Commands/ExplorerCommand.cs deleted file mode 100644 index f4b130b2..00000000 --- a/src/console/LibplanetConsole.Console.Explorer/Commands/ExplorerCommand.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.ComponentModel; -using JSSoft.Commands; -using LibplanetConsole.Common; -using LibplanetConsole.Console.Commands; -using Microsoft.Extensions.DependencyInjection; - -namespace LibplanetConsole.Console.Explorer.Commands; - -internal sealed partial class ExplorerCommand(NodeCommand nodeCommand, IApplication application) - : CommandMethodBase(nodeCommand) -{ - [CommandPropertyRequired(DefaultValue = "")] - [CommandSummary("The address of the node. If not specified, the current node is used.")] - public static string Address { get; set; } = string.Empty; - - [CommandMethod] - [CommandMethodProperty(nameof(Address))] - [CommandSummary("Start the explorer.")] - [Category("Explorer")] - public async Task StartAsync( - string endPoint = "", CancellationToken cancellationToken = default) - { - var node = application.GetNode(Address); - var explorer = node.GetRequiredService(); - if (endPoint != string.Empty) - { - explorer.EndPoint = EndPointUtility.Parse(endPoint); - } - - await explorer.StartAsync(cancellationToken); - } - - [CommandMethod] - [CommandMethodProperty(nameof(Address))] - [CommandSummary("Stop the explorer.")] - [Category("Explorer")] - public async Task StopAsync(CancellationToken cancellationToken = default) - { - var node = application.GetNode(Address); - var explorer = node.GetRequiredService(); - await explorer.StopAsync(cancellationToken); - } -} diff --git a/src/console/LibplanetConsole.Console.Explorer/Explorer.cs b/src/console/LibplanetConsole.Console.Explorer/Explorer.cs deleted file mode 100644 index fb3c2903..00000000 --- a/src/console/LibplanetConsole.Console.Explorer/Explorer.cs +++ /dev/null @@ -1,129 +0,0 @@ -using LibplanetConsole.Common; -using LibplanetConsole.Explorer; -using LibplanetConsole.Explorer.Services; -using Microsoft.Extensions.Logging; - -namespace LibplanetConsole.Console.Explorer; - -internal sealed class Explorer : NodeContentBase, IExplorer, IExplorerCallback -// , INodeContentService -{ - private readonly ILogger _logger; - private readonly ExecutionScope _executionScope = new(); - private readonly ExplorerSettings _settings; - private EndPoint _endPoint = EndPointUtility.NextEndPoint(); - // private RemoteService? _remoteService; - - public Explorer(INode node, ILogger logger, ExplorerSettings settings) - : base(node) - { - _settings = settings; - _logger = logger; - _logger.LogDebug("Explorer is created: {NodeAddress}", node.Address); - } - - public event EventHandler? Started; - - public event EventHandler? Stopped; - - public EndPoint EndPoint - { - get => _endPoint; - set - { - if (IsRunning == true) - { - throw new InvalidOperationException( - "Cannot change the endpoint while the explorer is running."); - } - - _endPoint = value; - } - } - - public ExplorerInfo Info { get; private set; } - - public bool IsRunning { get; private set; } - - // IRemoteService INodeContentService.RemoteService => RemoteService; - - // private IExplorerService Service => RemoteService.Service; - - // private RemoteService RemoteService - // => _remoteService ??= new RemoteService(this); - - public async Task StartAsync(CancellationToken cancellationToken) - { - if (IsRunning == true) - { - throw new InvalidOperationException("The explorer is already running."); - } - - using var scope = _executionScope.Enter(); - var nodeAddress = Node.Address; - var endPoint = EndPoint.ToString(); - var options = new ExplorerOptions - { - EndPoint = EndPoint, - }; - // Info = await Service.StartAsync(options, cancellationToken); - IsRunning = true; - _logger.LogDebug("Explorer is started: {NodeAddress} {EndPoint}", nodeAddress, endPoint); - Started?.Invoke(this, EventArgs.Empty); - } - - public async Task StopAsync(CancellationToken cancellationToken) - { - if (IsRunning != true) - { - throw new InvalidOperationException("The explorer is not running."); - } - - using var scope = _executionScope.Enter(); - // await Service.StopAsync(cancellationToken); - IsRunning = false; - _logger.LogDebug("Explorer is stopped.: {NodeAddress}", Node.Address); - Stopped?.Invoke(this, EventArgs.Empty); - } - - void IExplorerCallback.OnStarted(ExplorerInfo explorerInfo) - { - if (_executionScope.IsExecuting != true) - { - var message = "Explorer is started by the remote service: {NodeAddress} {EndPoint}"; - var nodeAddress = Node.Address; - var endPoint = EndPoint.ToString(); - Info = explorerInfo; - IsRunning = true; - _logger.LogDebug(message, nodeAddress, endPoint); - Started?.Invoke(this, EventArgs.Empty); - } - } - - void IExplorerCallback.OnStopped() - { - if (_executionScope.IsExecuting != true) - { - IsRunning = false; - _logger.LogDebug("Explorer is stopped by the remote service: {NodeAddress}", Node.Address); - Stopped?.Invoke(this, EventArgs.Empty); - } - } - - protected async override void OnNodeAttached() - { - base.OnNodeAttached(); - // Info = await Service.GetInfoAsync(cancellationToken: default); - IsRunning = Info.IsRunning; - } - - protected async override void OnNodeStarted() - { - base.OnNodeStarted(); - - if (_settings.UseExplorer == true) - { - await StartAsync(cancellationToken: default); - } - } -} diff --git a/src/console/LibplanetConsole.Console.Explorer/ExplorerInfoProvider.cs b/src/console/LibplanetConsole.Console.Explorer/ExplorerInfoProvider.cs deleted file mode 100644 index c44b33d5..00000000 --- a/src/console/LibplanetConsole.Console.Explorer/ExplorerInfoProvider.cs +++ /dev/null @@ -1,21 +0,0 @@ -using LibplanetConsole.Common; - -namespace LibplanetConsole.Console.Explorer; - -internal sealed class ExplorerInfoProvider : InfoProviderBase -{ - public ExplorerInfoProvider() - : base("Explorer") - { - } - - protected override object? GetInfo(Explorer obj) - { - return new - { - obj.EndPoint, - obj.IsRunning, - Url = obj.IsRunning ? $"http://{obj.EndPoint}/ui/playground" : string.Empty, - }; - } -} diff --git a/src/console/LibplanetConsole.Console.Explorer/ExplorerSettings.cs b/src/console/LibplanetConsole.Console.Explorer/ExplorerSettings.cs deleted file mode 100644 index b2bd4394..00000000 --- a/src/console/LibplanetConsole.Console.Explorer/ExplorerSettings.cs +++ /dev/null @@ -1,12 +0,0 @@ -using JSSoft.Commands; -using LibplanetConsole.Framework; - -namespace LibplanetConsole.Console.Explorer; - -[ApplicationSettings] -internal sealed class ExplorerSettings -{ - [CommandPropertySwitch("explorer")] - [CommandSummary("If set, the explorer service will also start when the node starts.")] - public bool UseExplorer { get; init; } -} diff --git a/src/console/LibplanetConsole.Console.Explorer/IExplorer.cs b/src/console/LibplanetConsole.Console.Explorer/IExplorer.cs deleted file mode 100644 index 4d9dbcd4..00000000 --- a/src/console/LibplanetConsole.Console.Explorer/IExplorer.cs +++ /dev/null @@ -1,20 +0,0 @@ -using LibplanetConsole.Explorer; - -namespace LibplanetConsole.Console.Explorer; - -public interface IExplorer -{ - event EventHandler? Started; - - event EventHandler? Stopped; - - EndPoint EndPoint { get; set; } - - ExplorerInfo Info { get; } - - bool IsRunning { get; } - - Task StartAsync(CancellationToken cancellationToken); - - Task StopAsync(CancellationToken cancellationToken); -} diff --git a/src/console/LibplanetConsole.Console.Explorer/LibplanetConsole.Console.Explorer.csproj b/src/console/LibplanetConsole.Console.Explorer/LibplanetConsole.Console.Explorer.csproj deleted file mode 100644 index 1ebe5913..00000000 --- a/src/console/LibplanetConsole.Console.Explorer/LibplanetConsole.Console.Explorer.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/console/LibplanetConsole.Console.Explorer/ServiceCollectionExtensions.cs b/src/console/LibplanetConsole.Console.Explorer/ServiceCollectionExtensions.cs deleted file mode 100644 index 5db9f26b..00000000 --- a/src/console/LibplanetConsole.Console.Explorer/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -using JSSoft.Commands; -using LibplanetConsole.Common; -using LibplanetConsole.Console.Explorer.Commands; -using Microsoft.Extensions.DependencyInjection; - -namespace LibplanetConsole.Console.Explorer; - -public static class ServiceCollectionExtensions -{ - public static IServiceCollection AddExplorer(this IServiceCollection @this) - { - @this.AddScoped() - .AddScoped(s => s.GetRequiredService()) - .AddScoped(s => s.GetRequiredService()); - // .AddScoped(s => s.GetRequiredService()); - - @this.AddSingleton(); - - @this.AddSingleton(); - - return @this; - } -} diff --git a/src/console/LibplanetConsole.Console/ApplicationBase.cs b/src/console/LibplanetConsole.Console/ApplicationBase.cs index 2003dc97..dd1ccf7a 100644 --- a/src/console/LibplanetConsole.Console/ApplicationBase.cs +++ b/src/console/LibplanetConsole.Console/ApplicationBase.cs @@ -98,22 +98,22 @@ internal Node GetNode(string address) protected override async Task OnRunAsync(CancellationToken cancellationToken) { _logger.LogDebug("ConsoleContext is starting: {EndPoint}", _info.EndPoint); - _consoleContext = _serviceProvider.GetRequiredService(); - _consoleContext.EndPoint = _info.EndPoint; - _closeToken = await _consoleContext.StartAsync(cancellationToken: default); + // _consoleContext = _serviceProvider.GetRequiredService(); + // _consoleContext.EndPoint = _info.EndPoint; + // _closeToken = await _consoleContext.StartAsync(cancellationToken: default); _logger.LogDebug("ConsoleContext is started: {EndPoint}", _info.EndPoint); await base.OnRunAsync(cancellationToken); } protected override async ValueTask OnDisposeAsync() { - if (_consoleContext is not null) - { - _logger.LogDebug("ConsoleContext is closing: {EndPoint}", _info.EndPoint); - await _consoleContext.CloseAsync(_closeToken, CancellationToken.None); - _consoleContext = null; - _logger.LogDebug("ConsoleContext is closed: {EndPoint}", _info.EndPoint); - } + // if (_consoleContext is not null) + // { + // _logger.LogDebug("ConsoleContext is closing: {EndPoint}", _info.EndPoint); + // await _consoleContext.CloseAsync(_closeToken, CancellationToken.None); + // _consoleContext = null; + // _logger.LogDebug("ConsoleContext is closed: {EndPoint}", _info.EndPoint); + // } await base.OnDisposeAsync(); } diff --git a/src/console/LibplanetConsole.Console/Client.cs b/src/console/LibplanetConsole.Console/Client.cs index fe99a67c..1370eb36 100644 --- a/src/console/LibplanetConsole.Console/Client.cs +++ b/src/console/LibplanetConsole.Console/Client.cs @@ -26,7 +26,7 @@ public Client(IServiceProvider serviceProvider, ClientOptions clientOptions) { _serviceProvider = serviceProvider; _clientOptions = clientOptions; - _remoteService = new(this); + // _remoteService = new(this); _logger = _serviceProvider.GetLogger(); PublicKey = clientOptions.PrivateKey.PublicKey; _logger.LogDebug("Client is created: {Address}", Address); diff --git a/src/console/LibplanetConsole.Console/LibplanetConsole.Console.csproj b/src/console/LibplanetConsole.Console/LibplanetConsole.Console.csproj index e0fa6a13..a3f56ff5 100644 --- a/src/console/LibplanetConsole.Console/LibplanetConsole.Console.csproj +++ b/src/console/LibplanetConsole.Console/LibplanetConsole.Console.csproj @@ -8,6 +8,11 @@ + + + + + diff --git a/src/console/LibplanetConsole.Console/Node.cs b/src/console/LibplanetConsole.Console/Node.cs index 1f3f54b8..12b7a130 100644 --- a/src/console/LibplanetConsole.Console/Node.cs +++ b/src/console/LibplanetConsole.Console/Node.cs @@ -32,8 +32,8 @@ public Node(IServiceProvider serviceProvider, NodeOptions nodeOptions) _serviceProvider = serviceProvider; _nodeOptions = nodeOptions; _logger = _serviceProvider.GetLogger(); - _remoteService = new(this); - _blockChainService = new(this); + // _remoteService = new(this); + // _blockChainService = new(this); PublicKey = nodeOptions.PrivateKey.PublicKey; _logger.LogDebug("Node is created: {Address}", Address); } diff --git a/src/node/LibplanetConsole.Node.Executable/EntryCommands/StartCommand.cs b/src/node/LibplanetConsole.Node.Executable/EntryCommands/StartCommand.cs index 54ae8129..e1114699 100644 --- a/src/node/LibplanetConsole.Node.Executable/EntryCommands/StartCommand.cs +++ b/src/node/LibplanetConsole.Node.Executable/EntryCommands/StartCommand.cs @@ -1,8 +1,12 @@ using System.ComponentModel; using JSSoft.Commands; +using LibplanetConsole.Common; using LibplanetConsole.DataAnnotations; using LibplanetConsole.Framework; +using LibplanetConsole.Node.Explorer; +using LibplanetConsole.Node.Services; using LibplanetConsole.Settings; +using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Node.Executable.EntryCommands; @@ -35,6 +39,8 @@ protected override async Task OnExecuteAsync(CancellationToken cancellationToken { try { + var builder = WebApplication.CreateBuilder(); + var settingsPath = Path.Combine(RepositoryPath, Repository.SettingsFileName); var applicationSettings = Load(settingsPath) with { @@ -42,17 +48,41 @@ protected override async Task OnExecuteAsync(CancellationToken cancellationToken NoREPL = NoREPL, }; var applicationOptions = applicationSettings.ToOptions(); - var serviceCollection = new ApplicationServiceCollection(_settingsCollection); - serviceCollection.AddNode(applicationOptions); - serviceCollection.AddApplication(applicationOptions); + foreach (var settings in _settingsCollection) + { + builder.Services.AddSingleton(settings.GetType(), settings); + } + + var (_, port) = EndPointUtility.GetHostAndPort(applicationOptions.EndPoint); + builder.WebHost.ConfigureKestrel(options => + { + // Setup a HTTP/2 endpoint without TLS. + options.ListenLocalhost(port, o => o.Protocols = HttpProtocols.Http2); + options.ListenLocalhost(port + 1, o => o.Protocols = HttpProtocols.Http1AndHttp2); + }); + + builder.Services.AddNode(applicationOptions); + builder.Services.AddApplication(applicationOptions); + builder.Services.AddExplorer(builder.Configuration); + + builder.Services.AddGrpc(); + + using var app = builder.Build(); + + app.UseNode(); + app.UseExplorer(); + app.MapGet("/", () => "123"); + app.UseAuthentication(); + app.UseAuthorization(); - await using var serviceProvider = serviceCollection.BuildServiceProvider(); var @out = Console.Out; - var application = serviceProvider.GetRequiredService(); + await app.StartAsync(cancellationToken); + var application = app.Services.GetRequiredService(); await @out.WriteLineAsync(); await application.RunAsync(); await @out.WriteLineAsync("\u001b0"); + await app.StopAsync(cancellationToken); } catch (CommandParsingException e) { diff --git a/src/node/LibplanetConsole.Node.Executable/ServiceCollectionExtensions.cs b/src/node/LibplanetConsole.Node.Executable/ServiceCollectionExtensions.cs index 857efc24..d11562b2 100644 --- a/src/node/LibplanetConsole.Node.Executable/ServiceCollectionExtensions.cs +++ b/src/node/LibplanetConsole.Node.Executable/ServiceCollectionExtensions.cs @@ -32,7 +32,8 @@ public static IServiceCollection AddApplication( @this.AddEvidence(); @this.AddExample(); - @this.AddExplorer(); + + @this.AddGrpc(); return @this; } diff --git a/src/node/LibplanetConsole.Node.Explorer/Commands/ExplorerCommand.cs b/src/node/LibplanetConsole.Node.Explorer/Commands/ExplorerCommand.cs deleted file mode 100644 index de2836e3..00000000 --- a/src/node/LibplanetConsole.Node.Explorer/Commands/ExplorerCommand.cs +++ /dev/null @@ -1,30 +0,0 @@ -using JSSoft.Commands; -using LibplanetConsole.Common; -using LibplanetConsole.Explorer; -using Microsoft.Extensions.DependencyInjection; - -namespace LibplanetConsole.Node.Explorer.Commands; - -[CommandSummary("Provides commands for the explorer(GraphQL).")] -internal sealed class ExplorerCommand(IServiceProvider serviceProvider) : CommandMethodBase -{ - [CommandMethod] - public async Task StartAsync( - string endPoint = "", CancellationToken cancellationToken = default) - { - var explorer = serviceProvider.GetRequiredService(); - var explorerOptions = new ExplorerOptions - { - EndPoint = EndPointUtility.ParseOrNext(endPoint), - }; - await explorer.StartAsync(explorerOptions, cancellationToken); - await Console.Out.WriteLineAsync($"http://{explorerOptions.EndPoint}/ui/playground"); - } - - [CommandMethod] - public async Task StopAsync(CancellationToken cancellationToken = default) - { - var explorer = serviceProvider.GetRequiredService(); - await explorer.StopAsync(cancellationToken); - } -} diff --git a/src/node/LibplanetConsole.Node.Explorer/Explorer.cs b/src/node/LibplanetConsole.Node.Explorer/Explorer.cs deleted file mode 100644 index 0befec35..00000000 --- a/src/node/LibplanetConsole.Node.Explorer/Explorer.cs +++ /dev/null @@ -1,77 +0,0 @@ -using Libplanet.Explorer; -using LibplanetConsole.Common; -using LibplanetConsole.Explorer; -using LibplanetConsole.Framework; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace LibplanetConsole.Node.Explorer; - -internal sealed class Explorer(INode node, ILogger logger, ExplorerSettings settings) - : IExplorer, IApplicationService -{ - private IWebHost? _webHost; - - public event EventHandler? Started; - - public event EventHandler? Stopped; - - public ExplorerInfo Info { get; private set; } - - public bool IsRunning => _webHost is not null; - - public async Task StartAsync( - ExplorerOptions options, CancellationToken cancellationToken) - { - if (_webHost is not null) - { - throw new InvalidOperationException("The explorer is already running."); - } - - var (host, port) = EndPointUtility.GetHostAndPort(options.EndPoint); - _webHost = WebHost.CreateDefaultBuilder() - .ConfigureServices(services => services.AddSingleton(node)) - .UseStartup>() - .UseUrls($"http://{host}:{port}/") - .Build(); - - await _webHost.StartAsync(cancellationToken); - Info = new() - { - EndPoint = options.EndPoint, - IsRunning = true, - Url = $"http://{host}:{port}/ui/playground", - }; - logger.LogDebug("Explorer is started: {EndPoint}", Info.EndPoint); - Started?.Invoke(this, EventArgs.Empty); - return Info; - } - - public async Task StopAsync(CancellationToken cancellationToken) - { - if (_webHost is null) - { - throw new InvalidOperationException("The explorer is not running."); - } - - await _webHost.StopAsync(cancellationToken); - _webHost = null; - Info = new() { }; - logger.LogDebug("Explorer is stopped."); - Stopped?.Invoke(this, EventArgs.Empty); - } - - async Task IApplicationService.InitializeAsync(CancellationToken cancellationToken) - { - if (settings.IsExplorerEnabled is true) - { - var options = new ExplorerOptions - { - EndPoint = EndPointUtility.ParseOrNext(settings.ExplorerEndPoint), - }; - await StartAsync(options, cancellationToken); - } - } -} diff --git a/src/node/LibplanetConsole.Node.Explorer/ExplorerInfoProvider.cs b/src/node/LibplanetConsole.Node.Explorer/ExplorerInfoProvider.cs deleted file mode 100644 index a04ecdd4..00000000 --- a/src/node/LibplanetConsole.Node.Explorer/ExplorerInfoProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -using LibplanetConsole.Common; - -namespace LibplanetConsole.Node.Explorer; - -internal sealed class ExplorerInfoProvider(Explorer explorer) - : InfoProviderBase(nameof(Explorer)) -{ - protected override object? GetInfo(IApplication obj) => explorer.Info; -} diff --git a/src/node/LibplanetConsole.Node.Explorer/ExplorerSettings.cs b/src/node/LibplanetConsole.Node.Explorer/ExplorerSettings.cs deleted file mode 100644 index 0d356e50..00000000 --- a/src/node/LibplanetConsole.Node.Explorer/ExplorerSettings.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.ComponentModel; -using System.Text.Json.Serialization; -using JSSoft.Commands; -using LibplanetConsole.Common.DataAnnotations; -using LibplanetConsole.Framework; - -namespace LibplanetConsole.Node.Explorer; - -[ApplicationSettings] -internal sealed class ExplorerSettings -{ - public const string Explorer = nameof(Explorer); - - [CommandPropertySwitch("explorer")] - [CommandSummary("")] - [JsonPropertyName("isEnabled")] - [Category(Explorer)] - public bool IsExplorerEnabled { get; init; } - - [CommandProperty("explorer-end-point", DefaultValue = "")] - [CommandSummary("")] - [CommandPropertyDependency(nameof(IsExplorerEnabled))] - [JsonPropertyName("endPoint")] - [EndPoint] - [Category(Explorer)] - public string ExplorerEndPoint { get; init; } = string.Empty; -} diff --git a/src/node/LibplanetConsole.Node.Explorer/IExplorer.cs b/src/node/LibplanetConsole.Node.Explorer/IExplorer.cs deleted file mode 100644 index bed1a0a4..00000000 --- a/src/node/LibplanetConsole.Node.Explorer/IExplorer.cs +++ /dev/null @@ -1,18 +0,0 @@ -using LibplanetConsole.Explorer; - -namespace LibplanetConsole.Node.Explorer; - -public interface IExplorer -{ - event EventHandler? Started; - - event EventHandler? Stopped; - - ExplorerInfo Info { get; } - - bool IsRunning { get; } - - Task StartAsync(ExplorerOptions options, CancellationToken cancellationToken); - - Task StopAsync(CancellationToken cancellationToken); -} diff --git a/src/node/LibplanetConsole.Node.Explorer/LibplanetConsole.Node.Explorer.csproj b/src/node/LibplanetConsole.Node.Explorer/LibplanetConsole.Node.Explorer.csproj index 92f756e4..9707aa2f 100644 --- a/src/node/LibplanetConsole.Node.Explorer/LibplanetConsole.Node.Explorer.csproj +++ b/src/node/LibplanetConsole.Node.Explorer/LibplanetConsole.Node.Explorer.csproj @@ -16,6 +16,11 @@ + + + + + diff --git a/src/node/LibplanetConsole.Node.Explorer/NodeEndpointRouteBuilderExtensions.cs b/src/node/LibplanetConsole.Node.Explorer/NodeEndpointRouteBuilderExtensions.cs new file mode 100644 index 00000000..44e5a84d --- /dev/null +++ b/src/node/LibplanetConsole.Node.Explorer/NodeEndpointRouteBuilderExtensions.cs @@ -0,0 +1,19 @@ +using Libplanet.Explorer; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; + +namespace LibplanetConsole.Node.Explorer; + +public static class NodeEndpointRouteBuilderExtensions +{ + public static IApplicationBuilder UseExplorer(this IApplicationBuilder @this) + { + var serviceProvider = @this.ApplicationServices; + var environment = serviceProvider.GetRequiredService(); + var startUp = serviceProvider.GetRequiredService>(); + startUp.Configure(@this, environment); + + return @this; + } +} diff --git a/src/node/LibplanetConsole.Node.Explorer/ServiceCollectionExtensions.cs b/src/node/LibplanetConsole.Node.Explorer/ServiceCollectionExtensions.cs index a4b9f453..7009da74 100644 --- a/src/node/LibplanetConsole.Node.Explorer/ServiceCollectionExtensions.cs +++ b/src/node/LibplanetConsole.Node.Explorer/ServiceCollectionExtensions.cs @@ -1,21 +1,20 @@ -using JSSoft.Commands; -using LibplanetConsole.Common; -using LibplanetConsole.Framework; -using LibplanetConsole.Node.Explorer.Commands; +using Libplanet.Explorer; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Node.Explorer; public static class ServiceCollectionExtensions { - public static IServiceCollection AddExplorer(this IServiceCollection @this) + public static IServiceCollection AddExplorer( + this IServiceCollection @this, IConfiguration configuration) { - @this.AddSingleton() - .AddSingleton(s => s.GetRequiredService()) - .AddSingleton(s => s.GetRequiredService()); - @this.AddSingleton(); - // @this.AddSingleton(); - @this.AddSingleton(); + var startup = new ExplorerStartup(configuration); + startup.ConfigureServices(@this); + @this.AddEndpointsApiExplorer(); + @this.AddSingleton(); + @this.AddSingleton(startup); + return @this; } } diff --git a/src/node/LibplanetConsole.Node.Explorer/Services/ExplorerService.cs b/src/node/LibplanetConsole.Node.Explorer/Services/ExplorerService.cs deleted file mode 100644 index 254ccaf0..00000000 --- a/src/node/LibplanetConsole.Node.Explorer/Services/ExplorerService.cs +++ /dev/null @@ -1,47 +0,0 @@ -using LibplanetConsole.Common.Services; -using LibplanetConsole.Explorer; -using LibplanetConsole.Explorer.Services; -using Microsoft.Extensions.Logging; - -// namespace LibplanetConsole.Node.Explorer.Services; - -// internal sealed class ExplorerService : LocalService, -// IExplorerService, IDisposable -// { -// private readonly INode _node; -// private readonly Explorer _explorer; -// private readonly ILogger _logger; - - public ExplorerService(INode node, Explorer explorer, ILogger logger) - { - _node = node; - _explorer = explorer; - _logger = logger; - _explorer.Started += ExplorerNode_Started; - _explorer.Stopped += ExplorerNode_Stopped; - _logger.LogDebug("ExplorerService is created: {NodeAddress}", node.Address); - } - - public void Dispose() - { - _explorer.Started -= ExplorerNode_Started; - _explorer.Stopped -= ExplorerNode_Stopped; - _logger.LogDebug("ExplorerService is disposed: {NodeAddress}", _node.Address); - } - -// public Task GetInfoAsync(CancellationToken cancellationToken) -// => Task.Run(() => _explorer.Info, cancellationToken); - -// public Task StartAsync( -// ExplorerOptions options, CancellationToken cancellationToken) -// => _explorer.StartAsync(options, cancellationToken); - -// public Task StopAsync(CancellationToken cancellationToken) -// => _explorer.StopAsync(cancellationToken); - -// private void ExplorerNode_Started(object? sender, EventArgs e) -// => Callback.OnStarted(_explorer.Info); - -// private void ExplorerNode_Stopped(object? sender, EventArgs e) -// => Callback.OnStopped(); -// } diff --git a/src/node/LibplanetConsole.Node/ApplicationBase.cs b/src/node/LibplanetConsole.Node/ApplicationBase.cs index 5292a919..bd79ac2f 100644 --- a/src/node/LibplanetConsole.Node/ApplicationBase.cs +++ b/src/node/LibplanetConsole.Node/ApplicationBase.cs @@ -54,24 +54,23 @@ protected ApplicationBase(IServiceProvider serviceProvider, ApplicationOptions o protected override async Task OnRunAsync(CancellationToken cancellationToken) { _logger.LogDebug("NodeContext is starting: {EndPoint}", _info.EndPoint); - _nodeContext = _serviceProvider.GetRequiredService(); - _nodeContext.EndPoint = _info.EndPoint; - _closeToken = await _nodeContext.StartAsync(cancellationToken: default); + // _nodeContext = _serviceProvider.GetRequiredService(); + // _nodeContext.EndPoint = _info.EndPoint; + // _closeToken = await _nodeContext.StartAsync(cancellationToken: default); _logger.LogDebug("NodeContext is started: {EndPoint}", _info.EndPoint); await base.OnRunAsync(cancellationToken); - await AutoStartAsync(cancellationToken); } protected override async ValueTask OnDisposeAsync() { await base.OnDisposeAsync(); - if (_nodeContext is not null) - { - _logger.LogDebug("NodeContext is closing: {EndPoint}", _info.EndPoint); - await _nodeContext.CloseAsync(_closeToken, cancellationToken: default); - _nodeContext = null; - _logger.LogDebug("NodeContext is closed: {EndPoint}", _info.EndPoint); - } + // if (_nodeContext is not null) + // { + // _logger.LogDebug("NodeContext is closing: {EndPoint}", _info.EndPoint); + // await _nodeContext.CloseAsync(_closeToken, cancellationToken: default); + // _nodeContext = null; + // _logger.LogDebug("NodeContext is closed: {EndPoint}", _info.EndPoint); + // } await _waitForExitTask; } @@ -81,15 +80,4 @@ private static async Task WaitForExit(Process process, Action cancelAction) await process.WaitForExitAsync(); cancelAction.Invoke(); } - - private async Task AutoStartAsync(CancellationToken cancellationToken) - { - if (_info.SeedEndPoint is { } seedEndPoint) - { - _logger.LogDebug("Node auto-starting: {EndPoint}", _info.EndPoint); - _node.SeedEndPoint = seedEndPoint; - await _node.StartAsync(cancellationToken); - _logger.LogDebug("Node auto-started: {EndPoint}", _info.EndPoint); - } - } } diff --git a/src/node/LibplanetConsole.Node/LibplanetConsole.Node.csproj b/src/node/LibplanetConsole.Node/LibplanetConsole.Node.csproj index 7644f857..18cb001d 100644 --- a/src/node/LibplanetConsole.Node/LibplanetConsole.Node.csproj +++ b/src/node/LibplanetConsole.Node/LibplanetConsole.Node.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/node/LibplanetConsole.Node/Node.cs b/src/node/LibplanetConsole.Node/Node.cs index 98a07ace..2a19b582 100644 --- a/src/node/LibplanetConsole.Node/Node.cs +++ b/src/node/LibplanetConsole.Node/Node.cs @@ -1,6 +1,8 @@ using System.Collections.Concurrent; using System.Security; using System.Security.Cryptography; +using Grpc.Core; +using Grpc.Net.Client; using Libplanet.Blockchain; using Libplanet.Blockchain.Renderers; using Libplanet.Net; @@ -11,7 +13,6 @@ using LibplanetConsole.Common.Exceptions; using LibplanetConsole.Common.Extensions; using LibplanetConsole.Seed; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace LibplanetConsole.Node; @@ -296,35 +297,19 @@ private static async Task CreateTransport( private static async Task GetSeedInfoAsync( EndPoint seedEndPoint, CancellationToken cancellationToken) { - // var remoteService = new RemoteService(); - // var remoteServiceContext = new RemoteServiceContext([remoteService]) - // { - // EndPoint = seedEndPoint, - // }; - // var closeToken = await remoteServiceContext.OpenAsync(cancellationToken); - // var service = remoteService.Service; - // var privateKey = new PrivateKey(); - // var publicKey = privateKey.PublicKey; - // try - // { - // for (var i = 0; i < 10; i++) - // { - // var seedInfo = await service.GetSeedAsync(publicKey, cancellationToken); - // if (Equals(seedInfo, SeedInfo.Empty) != true) - // { - // return seedInfo; - // } - - // await Task.Delay(500, cancellationToken); - // } - - // throw new InvalidOperationException("No seed information is available."); - // } - // finally - // { - // await remoteServiceContext.CloseAsync(closeToken, cancellationToken); - // } - throw new NotImplementedException(); + var address = $"http://{EndPointUtility.ToString(seedEndPoint)}"; + var channelOptions = new GrpcChannelOptions + { + }; + using var channel = GrpcChannel.ForAddress(address, channelOptions); + var client = new Seed.Grpc.SeedGrpcService.SeedGrpcServiceClient(channel); + var request = new Seed.Grpc.GetSeedRequest + { + PublicKey = new PrivateKey().PublicKey.ToHex(compress: true), + }; + + var response = await client.GetSeedAsync(request, cancellationToken: cancellationToken); + return response.SeedResult; } private void UpdateNodeInfo() diff --git a/src/node/LibplanetConsole.Node/NodeEndpointRouteBuilderExtensions.cs b/src/node/LibplanetConsole.Node/NodeEndpointRouteBuilderExtensions.cs new file mode 100644 index 00000000..19306bb1 --- /dev/null +++ b/src/node/LibplanetConsole.Node/NodeEndpointRouteBuilderExtensions.cs @@ -0,0 +1,17 @@ +using LibplanetConsole.Node.Services; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing; + +namespace LibplanetConsole.Node; + +public static class NodeEndpointRouteBuilderExtensions +{ + public static IEndpointRouteBuilder UseNode(this IEndpointRouteBuilder @this) + { + @this.MapGrpcService(); + @this.MapGrpcService(); + @this.MapGrpcService(); + + return @this; + } +} diff --git a/src/node/LibplanetConsole.Node/NodeHostedService.cs b/src/node/LibplanetConsole.Node/NodeHostedService.cs new file mode 100644 index 00000000..026c69e3 --- /dev/null +++ b/src/node/LibplanetConsole.Node/NodeHostedService.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace LibplanetConsole.Node; + +internal sealed class NodeHostedService( + IHostApplicationLifetime applicationLifetime, Node node, ILogger logger) + : IHostedService +{ + public Task StartAsync(CancellationToken cancellationToken) + { + applicationLifetime.ApplicationStarted.Register(async () => + { + logger.LogInformation("Application started."); + if (node.SeedEndPoint is { } seedEndPoint) + { + logger.LogDebug("Node auto-starting"); + node.SeedEndPoint = seedEndPoint; + await node.StartAsync(cancellationToken); + logger.LogDebug("Node auto-started"); + } + }); + return Task.CompletedTask; + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + if (node.IsRunning is true) + { + await node.StopAsync(cancellationToken); + } + } +} diff --git a/src/node/LibplanetConsole.Node/SeedService.cs b/src/node/LibplanetConsole.Node/SeedService.cs new file mode 100644 index 00000000..f12f22c6 --- /dev/null +++ b/src/node/LibplanetConsole.Node/SeedService.cs @@ -0,0 +1,66 @@ +using LibplanetConsole.Seed; +using Microsoft.Extensions.Hosting; +using static LibplanetConsole.Common.EndPointUtility; + +namespace LibplanetConsole.Node; + +internal sealed class SeedService(IApplication application) : ISeedService, IHostedService +{ + private readonly PrivateKey _seedNodePrivateKey = new(); + private SeedNode? _blocksyncSeedNode; + private SeedNode? _consensusSeedNode; + + public Task GetSeedAsync( + PublicKey publicKey, CancellationToken cancellationToken) + { + if (_blocksyncSeedNode is null || _consensusSeedNode is null) + { + throw new InvalidOperationException("The SeedService is not running."); + } + + var seedPeer = _blocksyncSeedNode.BoundPeer; + var consensusSeedPeer = _consensusSeedNode.BoundPeer; + var seedInfo = new SeedInfo + { + BlocksyncSeedPeer = seedPeer, + ConsensusSeedPeer = consensusSeedPeer, + }; + + return Task.Run(() => seedInfo, cancellationToken); + } + + async Task IHostedService.StartAsync(CancellationToken cancellationToken) + { + var info = application.Info; + if (CompareEndPoint(info.SeedEndPoint, info.EndPoint) is true) + { + _blocksyncSeedNode = new SeedNode(new() + { + PrivateKey = _seedNodePrivateKey, + EndPoint = NextEndPoint(), + }); + _consensusSeedNode = new SeedNode(new() + { + PrivateKey = _seedNodePrivateKey, + EndPoint = NextEndPoint(), + }); + await _blocksyncSeedNode.StartAsync(cancellationToken); + await _consensusSeedNode.StartAsync(cancellationToken); + } + } + + async Task IHostedService.StopAsync(CancellationToken cancellationToken) + { + if (_blocksyncSeedNode is not null) + { + await _blocksyncSeedNode.StopAsync(cancellationToken: default); + _blocksyncSeedNode = null; + } + + if (_consensusSeedNode is not null) + { + await _consensusSeedNode.StopAsync(cancellationToken: default); + _consensusSeedNode = null; + } + } +} diff --git a/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs b/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs index a19c5e41..586c1d12 100644 --- a/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs +++ b/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs @@ -3,6 +3,7 @@ using LibplanetConsole.Framework; using LibplanetConsole.Node.Commands; using LibplanetConsole.Node.Services; +using LibplanetConsole.Seed; using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Node; @@ -12,13 +13,14 @@ public static class ServiceCollectionExtensions public static IServiceCollection AddNode( this IServiceCollection @this, ApplicationOptions options) { + @this.AddSingleton() + .AddSingleton(s => s.GetRequiredService()) + .AddHostedService(s => s.GetRequiredService()); @this.AddSingleton(s => new Node(s, options)) .AddSingleton(s => s.GetRequiredService()) .AddSingleton(s => s.GetRequiredService()); + @this.AddHostedService(); // @this.AddSingleton(); - // @this.AddSingleton() - // .AddSingleton(s => s.GetRequiredService()) - // .AddSingleton(s => s.GetRequiredService()); // @this.AddSingleton(); // @this.AddSingleton(); @this.AddSingleton(); @@ -31,6 +33,7 @@ public static IServiceCollection AddNode( @this.AddSingleton(); @this.AddSingleton(); @this.AddSingleton(); + return @this; } } diff --git a/src/node/LibplanetConsole.Node/Services/BlockChainGrpcServiceV1.cs b/src/node/LibplanetConsole.Node/Services/BlockChainGrpcServiceV1.cs new file mode 100644 index 00000000..0936789c --- /dev/null +++ b/src/node/LibplanetConsole.Node/Services/BlockChainGrpcServiceV1.cs @@ -0,0 +1,63 @@ +using Grpc.Core; +using LibplanetConsole.Node.Grpc; + +namespace LibplanetConsole.Node.Services; + +internal sealed class BlockChainGrpcServiceV1(Node node, IBlockChain blockChain) + : BlockChainGrpcService.BlockChainGrpcServiceBase +{ + private static readonly Codec _codec = new(); + + public async override Task SendTransaction( + SendTransactionRequest request, ServerCallContext context) + { + var tx = Transaction.Deserialize(request.TransactionData.ToByteArray()); + await node.AddTransactionAsync(tx, context.CancellationToken); + return new SendTransactionResponse { TxId = tx.Id.ToHex() }; + } + + public async override Task GetNextNonce( + GetNextNonceRequest request, ServerCallContext context) + { + var address = new Address(request.Address); + var nonce = await blockChain.GetNextNonceAsync(address, context.CancellationToken); + return new GetNextNonceResponse { Nonce = nonce }; + } + + public override async Task GetTipHash( + GetTipHashRequest request, ServerCallContext context) + { + var blockHash = await blockChain.GetTipHashAsync(context.CancellationToken); + return new GetTipHashResponse { BlockHash = blockHash.ToString() }; + } + + public override async Task GetState( + GetStateRequest request, ServerCallContext context) + { + BlockHash? blockHash = request.BlockHash == string.Empty + ? null : BlockHash.FromString(request.BlockHash); + var accountAddress = new Address(request.AccountAddress); + var address = new Address(request.Address); + var value = await blockChain.GetStateAsync( + blockHash, accountAddress, address, context.CancellationToken); + var state = _codec.Encode(value); + return new GetStateResponse { StateData = Google.Protobuf.ByteString.CopyFrom(state) }; + } + + public override async Task GetBlockHash( + GetBlockHashRequest request, ServerCallContext context) + { + var height = request.Height; + var blockHash = await blockChain.GetBlockHashAsync(height, context.CancellationToken); + return new GetBlockHashResponse { BlockHash = blockHash.ToString() }; + } + + public override async Task GetAction( + GetActionRequest request, ServerCallContext context) + { + var txId = TxId.FromHex(request.TxId); + var actionIndex = request.ActionIndex; + var action = await node.GetActionAsync(txId, actionIndex, context.CancellationToken); + return new GetActionResponse { ActionData = Google.Protobuf.ByteString.CopyFrom(action) }; + } +} diff --git a/src/node/LibplanetConsole.Node/Services/BlockChainService.cs b/src/node/LibplanetConsole.Node/Services/BlockChainService.cs deleted file mode 100644 index 5d3d1fcc..00000000 --- a/src/node/LibplanetConsole.Node/Services/BlockChainService.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Security.Cryptography; -using LibplanetConsole.Common.Extensions; -using LibplanetConsole.Common.Services; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -// namespace LibplanetConsole.Node.Services; - -// internal sealed class BlockChainService -// : LocalService, IBlockChainService -// { -// private static readonly Codec _codec = new(); -// private readonly ILogger _logger; -// private readonly Node _node; - -// public BlockChainService(Node node) -// { -// _node = node; -// _logger = node.GetLogger(); -// _node.BlockAppended += Node_BlockAppended; -// } - -// public async Task SendTransactionAsync( -// byte[] transaction, CancellationToken cancellationToken) -// { -// var tx = Transaction.Deserialize(transaction); -// await _node.AddTransactionAsync(tx, cancellationToken); -// return tx.Id; -// } - -// public Task GetNextNonceAsync(Address address, CancellationToken cancellationToken) -// => _node.GetNextNonceAsync(address, cancellationToken); - -// public Task GetTipHashAsync(CancellationToken cancellationToken) -// => _node.GetTipHashAsync(cancellationToken); - -// public async Task GetStateAsync( -// BlockHash? blockHash, -// Address accountAddress, -// Address address, -// CancellationToken cancellationToken) -// { -// var value = await _node.GetStateAsync( -// blockHash, accountAddress, address, cancellationToken); -// return _codec.Encode(value); -// } - -// public async Task GetStateByStateRootHashAsync( -// HashDigest stateRootHash, -// Address accountAddress, -// Address address, -// CancellationToken cancellationToken) -// { -// var value = await _node.GetStateByStateRootHashAsync( -// stateRootHash, accountAddress, address, cancellationToken); -// return _codec.Encode(value); -// } - -// public Task GetBlockHashAsync(long height, CancellationToken cancellationToken) -// => _node.GetBlockHashAsync(height, cancellationToken); - -// public Task GetActionAsync( -// TxId txId, int actionIndex, CancellationToken cancellationToken) -// => _node.GetActionAsync(txId, actionIndex, cancellationToken); - -// private void Node_BlockAppended(object? sender, BlockEventArgs e) -// { -// var blockInfo = e.BlockInfo; -// Callback.OnBlockAppended(blockInfo); -// _logger.LogDebug("Callback.OnBlockAppended: {BlockHash}", blockInfo.Hash); -// } -//} diff --git a/src/node/LibplanetConsole.Node/Services/NodeContext.cs b/src/node/LibplanetConsole.Node/Services/NodeContext.cs deleted file mode 100644 index c9355c47..00000000 --- a/src/node/LibplanetConsole.Node/Services/NodeContext.cs +++ /dev/null @@ -1,6 +0,0 @@ -// namespace LibplanetConsole.Node.Services; - -// internal sealed class NodeContext(IEnumerable localServices) -// : LocalServiceContext(localServices) -// { -// } diff --git a/src/node/LibplanetConsole.Node/Services/NodeGrpcServiceV1.cs b/src/node/LibplanetConsole.Node/Services/NodeGrpcServiceV1.cs new file mode 100644 index 00000000..73e74cbc --- /dev/null +++ b/src/node/LibplanetConsole.Node/Services/NodeGrpcServiceV1.cs @@ -0,0 +1,29 @@ +using Grpc.Core; +using LibplanetConsole.Node.Grpc; + +namespace LibplanetConsole.Node.Services; + +public sealed class NodeGrpcServiceV1(INode node) : NodeGrpcService.NodeGrpcServiceBase +{ + public override async Task Start(StartRequest request, ServerCallContext context) + { + await node.StartAsync(context.CancellationToken); + return new StartResponse { NodeInfo = node.Info }; + } + + public async override Task Stop(StopRequest request, ServerCallContext context) + { + await node.StopAsync(context.CancellationToken); + return new StopResponse(); + } + + public override Task GetInfo(GetInfoRequest request, ServerCallContext context) + { + GetInfoResponse Action() => new() + { + NodeInfo = node.Info, + }; + + return Task.Run(Action, context.CancellationToken); + } +} diff --git a/src/node/LibplanetConsole.Node/Services/NodeService.cs b/src/node/LibplanetConsole.Node/Services/NodeService.cs deleted file mode 100644 index 24f33692..00000000 --- a/src/node/LibplanetConsole.Node/Services/NodeService.cs +++ /dev/null @@ -1,36 +0,0 @@ -using LibplanetConsole.Common; -using LibplanetConsole.Common.Services; -using Microsoft.Extensions.Logging; - -// namespace LibplanetConsole.Node.Services; - -// internal sealed class NodeService : LocalService, INodeService -// { -// private readonly Node _node; -// private readonly ILogger _logger; - - public NodeService(Node node, ILogger logger) - { - _node = node; - _logger = logger; - _node.Started += (s, e) => Callback.OnStarted(_node.Info); - _node.Stopped += (s, e) => Callback.OnStopped(); - } - -// public async Task GetInfoAsync(CancellationToken cancellationToken) -// { -// await Task.CompletedTask; -// return _node.Info; -// } - - public async Task StartAsync(string seedEndPoint, CancellationToken cancellationToken) - { - _node.SeedEndPoint = EndPointUtility.Parse(seedEndPoint); - await _node.StartAsync(cancellationToken); - _logger.LogInformation("Node started."); - return _node.Info; - } - -// public Task StopAsync(CancellationToken cancellationToken) -// => _node.StopAsync(cancellationToken); -// } diff --git a/src/node/LibplanetConsole.Node/Services/SeedGrpcServiceV1.cs b/src/node/LibplanetConsole.Node/Services/SeedGrpcServiceV1.cs new file mode 100644 index 00000000..2886e272 --- /dev/null +++ b/src/node/LibplanetConsole.Node/Services/SeedGrpcServiceV1.cs @@ -0,0 +1,20 @@ +using Grpc.Core; +using LibplanetConsole.Seed; +using LibplanetConsole.Seed.Grpc; + +namespace LibplanetConsole.Node.Services; + +public sealed class SeedGrpcServiceV1(ISeedService seedService) + : SeedGrpcService.SeedGrpcServiceBase +{ + public async override Task GetSeed( + GetSeedRequest request, ServerCallContext context) + { + var publicKey = new PrivateKey().PublicKey; + var seedInfo = await seedService.GetSeedAsync(publicKey, context.CancellationToken); + return new GetSeedResponse + { + SeedResult = seedInfo, + }; + } +} diff --git a/src/node/LibplanetConsole.Node/Services/SeedService.cs b/src/node/LibplanetConsole.Node/Services/SeedService.cs deleted file mode 100644 index 3134362d..00000000 --- a/src/node/LibplanetConsole.Node/Services/SeedService.cs +++ /dev/null @@ -1,67 +0,0 @@ -// using LibplanetConsole.Framework; -// using LibplanetConsole.Seed; -// using static LibplanetConsole.Common.EndPointUtility; - -// namespace LibplanetConsole.Node.Services; - -// internal sealed class SeedService(IApplication application) -// : LocalService, ISeedService, IApplicationService, IAsyncDisposable -// { -// private readonly PrivateKey _seedNodePrivateKey = new(); -// private SeedNode? _blocksyncSeedNode; -// private SeedNode? _consensusSeedNode; - -// public Task GetSeedAsync( -// PublicKey publicKey, CancellationToken cancellationToken) -// { -// if (_blocksyncSeedNode is null || _consensusSeedNode is null) -// { -// throw new InvalidOperationException("The SeedService is not running."); -// } - -// var seedPeer = _blocksyncSeedNode.BoundPeer; -// var consensusSeedPeer = _consensusSeedNode.BoundPeer; -// var seedInfo = new SeedInfo -// { -// BlocksyncSeedPeer = seedPeer, -// ConsensusSeedPeer = consensusSeedPeer, -// }; - -// return Task.Run(() => seedInfo, cancellationToken); -// } - -// async ValueTask IAsyncDisposable.DisposeAsync() -// { -// if (_blocksyncSeedNode is not null) -// { -// await _blocksyncSeedNode.StopAsync(cancellationToken: default); -// _blocksyncSeedNode = null; -// } - -// if (_consensusSeedNode is not null) -// { -// await _consensusSeedNode.StopAsync(cancellationToken: default); -// _consensusSeedNode = null; -// } -// } - -// async Task IApplicationService.InitializeAsync(CancellationToken cancellationToken) -// { -// var info = application.Info; -// if (CompareEndPoint(info.SeedEndPoint, info.EndPoint) is true) -// { -// _blocksyncSeedNode = new SeedNode(new() -// { -// PrivateKey = _seedNodePrivateKey, -// EndPoint = NextEndPoint(), -// }); -// _consensusSeedNode = new SeedNode(new() -// { -// PrivateKey = _seedNodePrivateKey, -// EndPoint = NextEndPoint(), -// }); -// await _blocksyncSeedNode.StartAsync(cancellationToken); -// await _consensusSeedNode.StartAsync(cancellationToken); -// } -// } -// } diff --git a/src/shared/LibplanetConsole.Explorer/ExplorerInfo.cs b/src/shared/LibplanetConsole.Explorer/ExplorerInfo.cs deleted file mode 100644 index 113bd3ba..00000000 --- a/src/shared/LibplanetConsole.Explorer/ExplorerInfo.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Text.Json.Serialization; -using LibplanetConsole.Common.Converters; - -namespace LibplanetConsole.Explorer; - -public readonly record struct ExplorerInfo -{ - [JsonConverter(typeof(EndPointJsonConverter))] - public EndPoint? EndPoint { get; init; } - - public bool IsRunning { get; init; } - - public string Url { get; init; } -} diff --git a/src/shared/LibplanetConsole.Explorer/ExplorerOptions.cs b/src/shared/LibplanetConsole.Explorer/ExplorerOptions.cs deleted file mode 100644 index 717fc166..00000000 --- a/src/shared/LibplanetConsole.Explorer/ExplorerOptions.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Text.Json.Serialization; -using LibplanetConsole.Common.Converters; - -namespace LibplanetConsole.Explorer; - -public sealed record class ExplorerOptions -{ - [JsonConverter(typeof(EndPointJsonConverter))] - public required EndPoint EndPoint { get; init; } -} diff --git a/src/shared/LibplanetConsole.Explorer/Services/IExplorerCallback.cs b/src/shared/LibplanetConsole.Explorer/Services/IExplorerCallback.cs deleted file mode 100644 index fb510327..00000000 --- a/src/shared/LibplanetConsole.Explorer/Services/IExplorerCallback.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace LibplanetConsole.Explorer.Services; - -public interface IExplorerCallback -{ - void OnStarted(ExplorerInfo explorerInfo); - - void OnStopped(); -} diff --git a/src/shared/LibplanetConsole.Explorer/Services/IExplorerService.cs b/src/shared/LibplanetConsole.Explorer/Services/IExplorerService.cs deleted file mode 100644 index 4435ae85..00000000 --- a/src/shared/LibplanetConsole.Explorer/Services/IExplorerService.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace LibplanetConsole.Explorer.Services; - -public interface IExplorerService -{ - Task StartAsync(ExplorerOptions options, CancellationToken cancellationToken); - - Task StopAsync(CancellationToken cancellationToken); - - Task GetInfoAsync(CancellationToken cancellationToken); -} diff --git a/src/shared/LibplanetConsole.Node/NodeInfo.cs b/src/shared/LibplanetConsole.Node/NodeInfo.cs index 0c2337c9..0e88d2ad 100644 --- a/src/shared/LibplanetConsole.Node/NodeInfo.cs +++ b/src/shared/LibplanetConsole.Node/NodeInfo.cs @@ -1,3 +1,5 @@ +using GrpcNodeInfo = LibplanetConsole.Node.Grpc.NodeInfo; + namespace LibplanetConsole.Node; public readonly record struct NodeInfo @@ -25,4 +27,34 @@ public readonly record struct NodeInfo SwarmEndPoint = string.Empty, ConsensusEndPoint = string.Empty, }; + + public static implicit operator GrpcNodeInfo(NodeInfo nodeInfo) + { + return new GrpcNodeInfo + { + ProcessId = nodeInfo.ProcessId, + AppProtocolVersion = nodeInfo.AppProtocolVersion, + SwarmEndPoint = nodeInfo.SwarmEndPoint, + ConsensusEndPoint = nodeInfo.ConsensusEndPoint, + Address = nodeInfo.Address.ToHex(), + GenesisHash = nodeInfo.GenesisHash.ToString(), + TipHash = nodeInfo.TipHash.ToString(), + IsRunning = nodeInfo.IsRunning, + }; + } + + public static implicit operator NodeInfo(GrpcNodeInfo nodeInfo) + { + return new NodeInfo + { + ProcessId = nodeInfo.ProcessId, + AppProtocolVersion = nodeInfo.AppProtocolVersion, + SwarmEndPoint = nodeInfo.SwarmEndPoint, + ConsensusEndPoint = nodeInfo.ConsensusEndPoint, + Address = new Address(nodeInfo.Address), + GenesisHash = BlockHash.FromString(nodeInfo.GenesisHash), + TipHash = BlockHash.FromString(nodeInfo.TipHash), + IsRunning = nodeInfo.IsRunning, + }; + } } diff --git a/src/shared/LibplanetConsole.Node/Protos/BlockChainGrpcService.proto b/src/shared/LibplanetConsole.Node/Protos/BlockChainGrpcService.proto new file mode 100644 index 00000000..ea213b9a --- /dev/null +++ b/src/shared/LibplanetConsole.Node/Protos/BlockChainGrpcService.proto @@ -0,0 +1,64 @@ +syntax = "proto3"; + +option csharp_namespace = "LibplanetConsole.Node.Grpc"; + +package libplanet.console.node.v1; + +service BlockChainGrpcService { + rpc SendTransaction(SendTransactionRequest) returns (SendTransactionResponse); + rpc GetNextNonce(GetNextNonceRequest) returns (GetNextNonceResponse); + rpc GetTipHash(GetTipHashRequest) returns (GetTipHashResponse); + rpc GetState(GetStateRequest) returns (GetStateResponse); + rpc GetBlockHash(GetBlockHashRequest) returns (GetBlockHashResponse); + rpc GetAction(GetActionRequest) returns (GetActionResponse); +} + +message SendTransactionRequest { + bytes transactionData = 1; + } + + message SendTransactionResponse { + string txId = 1; + } + + message GetNextNonceRequest { + string address = 1; + } + + message GetNextNonceResponse { + int64 nonce = 1; + } + + message GetTipHashRequest { + } + + message GetTipHashResponse { + string blockHash = 1; + } + + message GetStateRequest { + optional string blockHash = 1; + string accountAddress = 2; + string address = 3; + } + + message GetStateResponse { + bytes stateData = 1; + } + + message GetBlockHashRequest { + int64 height = 1; + } + + message GetBlockHashResponse { + string blockHash = 1; + } + + message GetActionRequest { + string txId = 1; + int32 actionIndex = 2; + } + + message GetActionResponse { + bytes actionData = 1; + } diff --git a/src/shared/LibplanetConsole.Node/Protos/NodeGrpcService.proto b/src/shared/LibplanetConsole.Node/Protos/NodeGrpcService.proto new file mode 100644 index 00000000..f705636a --- /dev/null +++ b/src/shared/LibplanetConsole.Node/Protos/NodeGrpcService.proto @@ -0,0 +1,43 @@ +syntax = "proto3"; + +option csharp_namespace = "LibplanetConsole.Node.Grpc"; + +package libplanet.console.node.v1; + +service NodeGrpcService { + rpc Start(StartRequest) returns (StartResponse); + rpc Stop(StopRequest) returns (StopResponse); + rpc GetInfo(GetInfoRequest) returns (GetInfoResponse); +} + +message NodeInfo { + int32 processId = 1; + string appProtocolVersion = 2; + string swarmEndPoint = 3; + string consensusEndPoint = 4; + string address = 5; + string genesisHash = 6; + string tipHash = 7; + bool isRunning = 8; +} + +message StartRequest { + string seedEndPoint = 1; +} + +message StartResponse { + NodeInfo nodeInfo = 1; +} + +message StopRequest { +} + +message StopResponse { +} + +message GetInfoRequest { +} + +message GetInfoResponse { + NodeInfo nodeInfo = 1; +} diff --git a/src/shared/LibplanetConsole.Node/Services/NodeService.proto b/src/shared/LibplanetConsole.Node/Services/NodeService.proto deleted file mode 100644 index 48e40d6f..00000000 --- a/src/shared/LibplanetConsole.Node/Services/NodeService.proto +++ /dev/null @@ -1,44 +0,0 @@ -syntax = "proto3"; - -option csharp_namespace = "Libplanet.Node.API"; - -package libplanet.console.node.v1; - -service Node { - rpc GetGenesisBlock (GetGenesisBlockRequest) returns (GetGenesisBlockReply); - rpc GetTip(Empty) returns (GetTipReply); - rpc GetBlock(GetBlockRequest) returns (GetBlockReply); -} - -message Empty { -} - -message GetGenesisBlockRequest { -} - -message GetGenesisBlockReply { - string hash = 1; -} - -message GetTipReply { - string hash = 1; - int64 height = 2; -} - -message GetBlockRequest { - oneof block_identifier { - int64 height = 1; - string hash = 2; - } -} - -message GetBlockReply { - string hash = 1; - int64 height = 2; - string miner = 3; - string public_key = 4; - string previous_hash = 5; - string state_root_hash = 6; - string signature = 7; - int64 protocol_version = 8; -} \ No newline at end of file From 110140f0968e02e0f2c59e02e8a18aa4402e1522 Mon Sep 17 00:00:00 2001 From: s2quake Date: Sat, 5 Oct 2024 18:25:29 +0900 Subject: [PATCH 03/18] refactor: Remove examples --- libplanet-console.sln | 21 ----- .../Example.cs | 26 ------ .../ExampleCommand.cs | 14 ---- .../ExampleInfoProvider.cs | 15 ---- .../ExampleRemoteService.cs | 16 ---- .../ExampleService.cs | 12 --- .../ExampleSettings.cs | 12 --- .../IExample.cs | 8 -- .../LibplanetConsole.Client.Example.csproj | 11 --- .../ServiceCollectionExtensions.cs | 19 ----- .../LibplanetConsole.Client.Executable.csproj | 1 - .../ServiceCollectionExtensions.cs | 2 - .../ExampleClient.cs | 23 ------ .../ExampleClientCommand.cs | 39 --------- .../ExampleClientInfoProvider.cs | 20 ----- .../ExampleClientProcessArgumentProvider.cs | 13 --- .../ExampleClientSettings.cs | 12 --- .../ExampleNode.cs | 64 --------------- .../ExampleNodeCommand.cs | 81 ------------------- .../ExampleNodeInfoProvider.cs | 20 ----- .../ExampleNodeProcessArgumentProvider.cs | 13 --- .../ExampleSettings.cs | 12 --- .../IExampleClient.cs | 8 -- .../IExampleNode.cs | 18 ----- .../LibplanetConsole.Console.Example.csproj | 11 --- .../ServiceCollectionExtensions.cs | 24 ------ ...LibplanetConsole.Console.Executable.csproj | 2 - .../ServiceCollectionExtensions.cs | 2 - .../Commands/ExampleCommand.cs | 40 --------- .../LibplanetConsole.Node.Example/Example.cs | 43 ---------- .../ExampleInfoProvider.cs | 15 ---- .../ExampleService.cs | 42 ---------- .../ExampleSettings.cs | 16 ---- .../LibplanetConsole.Node.Example/IExample.cs | 20 ----- .../LibplanetConsole.Node.Example.csproj | 11 --- .../ServiceCollectionExtensions.cs | 19 ----- .../LibplanetConsole.Node.Executable.csproj | 1 - .../ServiceCollectionExtensions.cs | 2 - .../ServiceCollectionExtensions.cs | 5 -- .../Services/IExampleClientService.cs | 8 -- .../Services/IExampleNodeCallback.cs | 8 -- .../Services/IExampleNodeService.cs | 12 --- 42 files changed, 761 deletions(-) delete mode 100644 src/client/LibplanetConsole.Client.Example/Example.cs delete mode 100644 src/client/LibplanetConsole.Client.Example/ExampleCommand.cs delete mode 100644 src/client/LibplanetConsole.Client.Example/ExampleInfoProvider.cs delete mode 100644 src/client/LibplanetConsole.Client.Example/ExampleRemoteService.cs delete mode 100644 src/client/LibplanetConsole.Client.Example/ExampleService.cs delete mode 100644 src/client/LibplanetConsole.Client.Example/ExampleSettings.cs delete mode 100644 src/client/LibplanetConsole.Client.Example/IExample.cs delete mode 100644 src/client/LibplanetConsole.Client.Example/LibplanetConsole.Client.Example.csproj delete mode 100644 src/client/LibplanetConsole.Client.Example/ServiceCollectionExtensions.cs delete mode 100644 src/console/LibplanetConsole.Console.Example/ExampleClient.cs delete mode 100644 src/console/LibplanetConsole.Console.Example/ExampleClientCommand.cs delete mode 100644 src/console/LibplanetConsole.Console.Example/ExampleClientInfoProvider.cs delete mode 100644 src/console/LibplanetConsole.Console.Example/ExampleClientProcessArgumentProvider.cs delete mode 100644 src/console/LibplanetConsole.Console.Example/ExampleClientSettings.cs delete mode 100644 src/console/LibplanetConsole.Console.Example/ExampleNode.cs delete mode 100644 src/console/LibplanetConsole.Console.Example/ExampleNodeCommand.cs delete mode 100644 src/console/LibplanetConsole.Console.Example/ExampleNodeInfoProvider.cs delete mode 100644 src/console/LibplanetConsole.Console.Example/ExampleNodeProcessArgumentProvider.cs delete mode 100644 src/console/LibplanetConsole.Console.Example/ExampleSettings.cs delete mode 100644 src/console/LibplanetConsole.Console.Example/IExampleClient.cs delete mode 100644 src/console/LibplanetConsole.Console.Example/IExampleNode.cs delete mode 100644 src/console/LibplanetConsole.Console.Example/LibplanetConsole.Console.Example.csproj delete mode 100644 src/console/LibplanetConsole.Console.Example/ServiceCollectionExtensions.cs delete mode 100644 src/node/LibplanetConsole.Node.Example/Commands/ExampleCommand.cs delete mode 100644 src/node/LibplanetConsole.Node.Example/Example.cs delete mode 100644 src/node/LibplanetConsole.Node.Example/ExampleInfoProvider.cs delete mode 100644 src/node/LibplanetConsole.Node.Example/ExampleService.cs delete mode 100644 src/node/LibplanetConsole.Node.Example/ExampleSettings.cs delete mode 100644 src/node/LibplanetConsole.Node.Example/IExample.cs delete mode 100644 src/node/LibplanetConsole.Node.Example/LibplanetConsole.Node.Example.csproj delete mode 100644 src/node/LibplanetConsole.Node.Example/ServiceCollectionExtensions.cs delete mode 100644 src/shared/LibplanetConsole.Example/Services/IExampleClientService.cs delete mode 100644 src/shared/LibplanetConsole.Example/Services/IExampleNodeCallback.cs delete mode 100644 src/shared/LibplanetConsole.Example/Services/IExampleNodeService.cs diff --git a/libplanet-console.sln b/libplanet-console.sln index f2d86b23..98c7421f 100644 --- a/libplanet-console.sln +++ b/libplanet-console.sln @@ -35,12 +35,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibplanetConsole.Node.Execu EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibplanetConsole.Node", "src\node\LibplanetConsole.Node\LibplanetConsole.Node.csproj", "{217938CF-B2B8-41BB-A193-475430421265}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibplanetConsole.Node.Example", "src\node\LibplanetConsole.Node.Example\LibplanetConsole.Node.Example.csproj", "{6ECDC75B-1B38-4480-AD15-CE0F330CD70B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibplanetConsole.Client.Example", "src\client\LibplanetConsole.Client.Example\LibplanetConsole.Client.Example.csproj", "{2CA203CA-1980-4B11-82E1-0E5812D86C63}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibplanetConsole.Console.Example", "src\console\LibplanetConsole.Console.Example\LibplanetConsole.Console.Example.csproj", "{ACE75CD7-E4FD-4B9E-943E-EF3BB11DECDF}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibplanetConsole.Node.Explorer", "src\node\LibplanetConsole.Node.Explorer\LibplanetConsole.Node.Explorer.csproj", "{FEF8E9D4-CBB7-4EFC-A5C2-2C9E91498D79}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibplanetConsole.Common.Tests", "test\LibplanetConsole.Common.Tests\LibplanetConsole.Common.Tests.csproj", "{65341396-A058-4577-9B70-C1DD3D146501}" @@ -101,18 +95,6 @@ Global {217938CF-B2B8-41BB-A193-475430421265}.Debug|Any CPU.Build.0 = Debug|Any CPU {217938CF-B2B8-41BB-A193-475430421265}.Release|Any CPU.ActiveCfg = Release|Any CPU {217938CF-B2B8-41BB-A193-475430421265}.Release|Any CPU.Build.0 = Release|Any CPU - {6ECDC75B-1B38-4480-AD15-CE0F330CD70B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6ECDC75B-1B38-4480-AD15-CE0F330CD70B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6ECDC75B-1B38-4480-AD15-CE0F330CD70B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6ECDC75B-1B38-4480-AD15-CE0F330CD70B}.Release|Any CPU.Build.0 = Release|Any CPU - {2CA203CA-1980-4B11-82E1-0E5812D86C63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2CA203CA-1980-4B11-82E1-0E5812D86C63}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2CA203CA-1980-4B11-82E1-0E5812D86C63}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2CA203CA-1980-4B11-82E1-0E5812D86C63}.Release|Any CPU.Build.0 = Release|Any CPU - {ACE75CD7-E4FD-4B9E-943E-EF3BB11DECDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ACE75CD7-E4FD-4B9E-943E-EF3BB11DECDF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ACE75CD7-E4FD-4B9E-943E-EF3BB11DECDF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ACE75CD7-E4FD-4B9E-943E-EF3BB11DECDF}.Release|Any CPU.Build.0 = Release|Any CPU {FEF8E9D4-CBB7-4EFC-A5C2-2C9E91498D79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FEF8E9D4-CBB7-4EFC-A5C2-2C9E91498D79}.Debug|Any CPU.Build.0 = Debug|Any CPU {FEF8E9D4-CBB7-4EFC-A5C2-2C9E91498D79}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -160,9 +142,6 @@ Global {8749087F-5DE0-46AB-82D6-5009B3D9E2AF} = {1DEAA4CE-E29B-4379-BAF6-20B79A5946CB} {F5DCB11A-EA32-441A-AF34-B66359C3EA50} = {4A8F8EE9-769C-4C97-89BC-19D038E69998} {217938CF-B2B8-41BB-A193-475430421265} = {4A8F8EE9-769C-4C97-89BC-19D038E69998} - {6ECDC75B-1B38-4480-AD15-CE0F330CD70B} = {4A8F8EE9-769C-4C97-89BC-19D038E69998} - {2CA203CA-1980-4B11-82E1-0E5812D86C63} = {1DEAA4CE-E29B-4379-BAF6-20B79A5946CB} - {ACE75CD7-E4FD-4B9E-943E-EF3BB11DECDF} = {CAB76DA9-6E57-4422-98C6-DD2D6299F675} {FEF8E9D4-CBB7-4EFC-A5C2-2C9E91498D79} = {4A8F8EE9-769C-4C97-89BC-19D038E69998} {65341396-A058-4577-9B70-C1DD3D146501} = {56942891-CFBD-41E4-8881-47F455D7BEFD} {4C151EAE-2105-4DA1-B645-C09513EA8532} = {4A8F8EE9-769C-4C97-89BC-19D038E69998} diff --git a/src/client/LibplanetConsole.Client.Example/Example.cs b/src/client/LibplanetConsole.Client.Example/Example.cs deleted file mode 100644 index eba4f624..00000000 --- a/src/client/LibplanetConsole.Client.Example/Example.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace LibplanetConsole.Client.Example; - -internal sealed class Example( - IClient client, - // ExampleRemoteService remoteNodeService, - ExampleSettings settings) - : IExample -{ - // private readonly ExampleRemoteService _remoteNodeService = remoteNodeService; - - public Address Address => client.Address; - - public bool IsExample { get; } = settings.IsExample; - - // private IExampleNodeService Server => _remoteNodeService.Service; - - public void Subscribe() - { - // Server.Subscribe(Address); - } - - public void Unsubscribe() - { - // Server.Unsubscribe(Address); - } -} diff --git a/src/client/LibplanetConsole.Client.Example/ExampleCommand.cs b/src/client/LibplanetConsole.Client.Example/ExampleCommand.cs deleted file mode 100644 index 2b16993a..00000000 --- a/src/client/LibplanetConsole.Client.Example/ExampleCommand.cs +++ /dev/null @@ -1,14 +0,0 @@ -using JSSoft.Commands; - -namespace LibplanetConsole.Client.Example; - -[CommandSummary("Example client commands for a quick start.")] -internal sealed class ExampleCommand(IExample sampleClient) - : CommandMethodBase -{ - [CommandMethod] - public void Subscribe() => sampleClient.Subscribe(); - - [CommandMethod] - public void Unsubscribe() => sampleClient.Unsubscribe(); -} diff --git a/src/client/LibplanetConsole.Client.Example/ExampleInfoProvider.cs b/src/client/LibplanetConsole.Client.Example/ExampleInfoProvider.cs deleted file mode 100644 index 65d2a931..00000000 --- a/src/client/LibplanetConsole.Client.Example/ExampleInfoProvider.cs +++ /dev/null @@ -1,15 +0,0 @@ -using LibplanetConsole.Common; - -namespace LibplanetConsole.Client.Example; - -internal sealed class ExampleInfoProvider(Example exampleClient) - : InfoProviderBase(nameof(Example)) -{ - protected override object? GetInfo(IApplication obj) - { - return new - { - exampleClient.IsExample, - }; - } -} diff --git a/src/client/LibplanetConsole.Client.Example/ExampleRemoteService.cs b/src/client/LibplanetConsole.Client.Example/ExampleRemoteService.cs deleted file mode 100644 index 868e8dc7..00000000 --- a/src/client/LibplanetConsole.Client.Example/ExampleRemoteService.cs +++ /dev/null @@ -1,16 +0,0 @@ -// using LibplanetConsole.Common.Services; -// using LibplanetConsole.Example.Services; - -// namespace LibplanetConsole.Client.Example; - -// internal sealed class ExampleRemoteService -// : RemoteService, IExampleNodeCallback -// { -// public void OnSubscribed(Address address) -// { -// } - -// public void OnUnsubscribed(Address address) -// { -// } -// } diff --git a/src/client/LibplanetConsole.Client.Example/ExampleService.cs b/src/client/LibplanetConsole.Client.Example/ExampleService.cs deleted file mode 100644 index 43233e81..00000000 --- a/src/client/LibplanetConsole.Client.Example/ExampleService.cs +++ /dev/null @@ -1,12 +0,0 @@ -// using LibplanetConsole.Common.Services; -// using LibplanetConsole.Example.Services; - -// namespace LibplanetConsole.Client.Example; - -// internal sealed class ExampleService(IExample sampleClient) -// : LocalService, IExampleClientService -// { -// public void Subscribe() => sampleClient.Subscribe(); - -// public void Unsubscribe() => sampleClient.Unsubscribe(); -// } diff --git a/src/client/LibplanetConsole.Client.Example/ExampleSettings.cs b/src/client/LibplanetConsole.Client.Example/ExampleSettings.cs deleted file mode 100644 index 97b393ed..00000000 --- a/src/client/LibplanetConsole.Client.Example/ExampleSettings.cs +++ /dev/null @@ -1,12 +0,0 @@ -using JSSoft.Commands; -using LibplanetConsole.Framework; - -namespace LibplanetConsole.Client.Example; - -[ApplicationSettings] -internal sealed class ExampleSettings -{ - [CommandPropertySwitch("example")] - [CommandSummary("This is switch for example. not used in real.")] - public bool IsExample { get; init; } -} diff --git a/src/client/LibplanetConsole.Client.Example/IExample.cs b/src/client/LibplanetConsole.Client.Example/IExample.cs deleted file mode 100644 index 90bebc47..00000000 --- a/src/client/LibplanetConsole.Client.Example/IExample.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace LibplanetConsole.Client.Example; - -public interface IExample -{ - void Subscribe(); - - void Unsubscribe(); -} diff --git a/src/client/LibplanetConsole.Client.Example/LibplanetConsole.Client.Example.csproj b/src/client/LibplanetConsole.Client.Example/LibplanetConsole.Client.Example.csproj deleted file mode 100644 index efdb34f2..00000000 --- a/src/client/LibplanetConsole.Client.Example/LibplanetConsole.Client.Example.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/client/LibplanetConsole.Client.Example/ServiceCollectionExtensions.cs b/src/client/LibplanetConsole.Client.Example/ServiceCollectionExtensions.cs deleted file mode 100644 index b261ab6c..00000000 --- a/src/client/LibplanetConsole.Client.Example/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using JSSoft.Commands; -using LibplanetConsole.Common; -using Microsoft.Extensions.DependencyInjection; - -namespace LibplanetConsole.Client.Example; - -public static class ServiceCollectionExtensions -{ - public static IServiceCollection AddExample(this IServiceCollection @this) - { - @this.AddSingleton() - .AddSingleton(s => s.GetRequiredService()); - @this.AddSingleton(); - @this.AddSingleton(); - // @this.AddSingleton(); - // @this.AddSingleton(); - return @this; - } -} diff --git a/src/client/LibplanetConsole.Client.Executable/LibplanetConsole.Client.Executable.csproj b/src/client/LibplanetConsole.Client.Executable/LibplanetConsole.Client.Executable.csproj index 36188eb3..4d6dbbbc 100644 --- a/src/client/LibplanetConsole.Client.Executable/LibplanetConsole.Client.Executable.csproj +++ b/src/client/LibplanetConsole.Client.Executable/LibplanetConsole.Client.Executable.csproj @@ -2,7 +2,6 @@ - diff --git a/src/client/LibplanetConsole.Client.Executable/ServiceCollectionExtensions.cs b/src/client/LibplanetConsole.Client.Executable/ServiceCollectionExtensions.cs index 5952057d..669dff97 100644 --- a/src/client/LibplanetConsole.Client.Executable/ServiceCollectionExtensions.cs +++ b/src/client/LibplanetConsole.Client.Executable/ServiceCollectionExtensions.cs @@ -1,5 +1,4 @@ using JSSoft.Commands; -using LibplanetConsole.Client.Example; using LibplanetConsole.Client.Executable.Commands; using LibplanetConsole.Client.Executable.Tracers; using LibplanetConsole.Framework; @@ -27,7 +26,6 @@ public static IServiceCollection AddApplication( @this.AddSingleton(); @this.AddSingleton(); - @this.AddExample(); @this.AddLogging(options.LogPath, string.Empty); return @this; diff --git a/src/console/LibplanetConsole.Console.Example/ExampleClient.cs b/src/console/LibplanetConsole.Console.Example/ExampleClient.cs deleted file mode 100644 index 55d9ad6d..00000000 --- a/src/console/LibplanetConsole.Console.Example/ExampleClient.cs +++ /dev/null @@ -1,23 +0,0 @@ -using LibplanetConsole.Example.Services; - -namespace LibplanetConsole.Console.Example; - -internal sealed class ExampleClient(IClient client, ExampleClientSettings settings) - : ClientContentBase(client), IExampleClient -{ - // private readonly RemoteService _remoteService = new(); - - public bool IsExample { get; } = settings.IsClientExample; - - // private IExampleClientService Service => _remoteService.Service; - - public void Subscribe() - { - // Service.Subscribe(); - } - - public void Unsubscribe() - { - // Service.Unsubscribe(); - } -} diff --git a/src/console/LibplanetConsole.Console.Example/ExampleClientCommand.cs b/src/console/LibplanetConsole.Console.Example/ExampleClientCommand.cs deleted file mode 100644 index 7720f385..00000000 --- a/src/console/LibplanetConsole.Console.Example/ExampleClientCommand.cs +++ /dev/null @@ -1,39 +0,0 @@ -using JSSoft.Commands; - -namespace LibplanetConsole.Console.Example; - -[CommandSummary("Example client commands for a quick start.")] -internal sealed class ExampleClientCommand(IApplication application) : CommandMethodBase -{ - [CommandMethod] - public void Subscribe(string clientAddress) - { - var client = application.GetClient(clientAddress); - - if (client.GetService(typeof(IExampleClient)) is IExampleClient sampleClient) - { - sampleClient.Subscribe(); - } - else - { - throw new InvalidOperationException( - "The client does not support the sample client service."); - } - } - - [CommandMethod] - public void Unsubscribe(string clientAddress) - { - var client = application.GetClient(clientAddress); - - if (client.GetService(typeof(IExampleClient)) is IExampleClient sampleClient) - { - sampleClient.Unsubscribe(); - } - else - { - throw new InvalidOperationException( - "The client does not support the sample client service."); - } - } -} diff --git a/src/console/LibplanetConsole.Console.Example/ExampleClientInfoProvider.cs b/src/console/LibplanetConsole.Console.Example/ExampleClientInfoProvider.cs deleted file mode 100644 index 7e8d0b53..00000000 --- a/src/console/LibplanetConsole.Console.Example/ExampleClientInfoProvider.cs +++ /dev/null @@ -1,20 +0,0 @@ -using LibplanetConsole.Common; - -namespace LibplanetConsole.Console.Example; - -internal sealed class ExampleClientInfoProvider - : InfoProviderBase -{ - public ExampleClientInfoProvider() - : base(nameof(ExampleClient)) - { - } - - protected override object? GetInfo(ExampleClient obj) - { - return new - { - obj.IsExample, - }; - } -} diff --git a/src/console/LibplanetConsole.Console.Example/ExampleClientProcessArgumentProvider.cs b/src/console/LibplanetConsole.Console.Example/ExampleClientProcessArgumentProvider.cs deleted file mode 100644 index 8e24e182..00000000 --- a/src/console/LibplanetConsole.Console.Example/ExampleClientProcessArgumentProvider.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace LibplanetConsole.Console.Example; - -internal sealed class ExampleClientProcessArgumentProvider - : ProcessArgumentProviderBase -{ - protected override IEnumerable GetArguments(ExampleClient obj) - { - if (obj.IsExample == true) - { - yield return "--example"; - } - } -} diff --git a/src/console/LibplanetConsole.Console.Example/ExampleClientSettings.cs b/src/console/LibplanetConsole.Console.Example/ExampleClientSettings.cs deleted file mode 100644 index 073205c3..00000000 --- a/src/console/LibplanetConsole.Console.Example/ExampleClientSettings.cs +++ /dev/null @@ -1,12 +0,0 @@ -using JSSoft.Commands; -using LibplanetConsole.Framework; - -namespace LibplanetConsole.Console.Example; - -[ApplicationSettings] -internal sealed class ExampleClientSettings -{ - [CommandPropertySwitch("client-example")] - [CommandSummary("This is switch for example. not used in real.")] - public bool IsClientExample { get; init; } -} diff --git a/src/console/LibplanetConsole.Console.Example/ExampleNode.cs b/src/console/LibplanetConsole.Console.Example/ExampleNode.cs deleted file mode 100644 index 79a29fa4..00000000 --- a/src/console/LibplanetConsole.Console.Example/ExampleNode.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Text; -using LibplanetConsole.Common; -using LibplanetConsole.Example.Services; - -namespace LibplanetConsole.Console.Example; - -internal sealed class ExampleNode(INode node, ExampleSettings settings) - : NodeContentBase(node), IExampleNodeCallback, IExampleNode - // , INodeContentService -{ - private readonly StringBuilder _log = new(); - // private RemoteService? _remoteService; - - public event EventHandler>? Subscribed; - - public event EventHandler>? Unsubscribed; - - public int Count { get; private set; } - - public bool IsExample { get; } = settings.IsNodeExample; - - // IRemoteService INodeContentService.RemoteService => RemoteService; - - // private IExampleNodeService Service => RemoteService.Service; - - // private RemoteService RemoteService - // { - // get - // { - // // return _remoteService ??= new RemoteService(this); - // throw new NotImplementedException(); - // } - // } - - public Task GetAddressesAsync(CancellationToken cancellationToken) - { - // return Service.GetAddressesAsync(cancellationToken); - throw new NotImplementedException(); - } - - public void Subscribe(Address address) - { - // Service.Subscribe(address); - } - - public void Unsubscribe(Address address) - { - // Service.Unsubscribe(address); - } - - async void IExampleNodeCallback.OnSubscribed(Address address) - { - // Count = await Service.GetAddressCountAsync(CancellationToken.None); - _log.AppendLine($"{nameof(IExampleNodeCallback.OnSubscribed)}: {address}"); - Subscribed?.Invoke(this, new ItemEventArgs
(address)); - } - - async void IExampleNodeCallback.OnUnsubscribed(Address address) - { - // Count = await Service.GetAddressCountAsync(CancellationToken.None); - _log.AppendLine($"{nameof(IExampleNodeCallback.OnUnsubscribed)}: {address}"); - Unsubscribed?.Invoke(this, new ItemEventArgs
(address)); - } -} diff --git a/src/console/LibplanetConsole.Console.Example/ExampleNodeCommand.cs b/src/console/LibplanetConsole.Console.Example/ExampleNodeCommand.cs deleted file mode 100644 index 1a067df0..00000000 --- a/src/console/LibplanetConsole.Console.Example/ExampleNodeCommand.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System.Text; -using JSSoft.Commands; - -namespace LibplanetConsole.Console.Example; - -[CommandSummary("Example node commands for a quick start.")] -internal sealed class ExampleNodeCommand(IApplication application) : CommandMethodBase -{ - [CommandMethod] - public void Subscribe(string nodeAddress, string clientAddress) - { - var node = application.GetNode(nodeAddress); - var client = application.GetClient(clientAddress); - - if (node.GetService(typeof(IExampleNode)) is IExampleNode sampleNode) - { - sampleNode.Subscribe(client.Address); - } - else - { - throw new InvalidOperationException( - "The node does not support the sample node service."); - } - } - - [CommandMethod] - public void Unsubscribe(string nodeAddress, string clientAddress) - { - var node = application.GetNode(nodeAddress); - var client = application.GetClient(clientAddress); - - if (node.GetService(typeof(IExampleNode)) is IExampleNode sampleNode) - { - sampleNode.Unsubscribe(client.Address); - } - else - { - throw new InvalidOperationException( - "The node does not support the sample node service."); - } - } - - [CommandMethod] - public void Count(string nodeAddress) - { - var node = application.GetNode(nodeAddress); - - if (node.GetService(typeof(IExampleNode)) is IExampleNode sampleNode) - { - Out.WriteLine(sampleNode.Count); - } - else - { - throw new InvalidOperationException( - "The node does not support the sample node service."); - } - } - - [CommandMethod] - public async Task ListAsync(string nodeAddress, CancellationToken cancellationToken) - { - var node = application.GetNode(nodeAddress); - - if (node.GetService(typeof(IExampleNode)) is IExampleNode sampleNode) - { - var addresses = await sampleNode.GetAddressesAsync(cancellationToken); - var sb = new StringBuilder(); - foreach (var address in addresses) - { - sb.AppendLine(address.ToString()); - } - - await Out.WriteAsync(sb.ToString()); - } - else - { - throw new InvalidOperationException( - "The node does not support the sample node service."); - } - } -} diff --git a/src/console/LibplanetConsole.Console.Example/ExampleNodeInfoProvider.cs b/src/console/LibplanetConsole.Console.Example/ExampleNodeInfoProvider.cs deleted file mode 100644 index 26e12a9e..00000000 --- a/src/console/LibplanetConsole.Console.Example/ExampleNodeInfoProvider.cs +++ /dev/null @@ -1,20 +0,0 @@ -using LibplanetConsole.Common; - -namespace LibplanetConsole.Console.Example; - -internal sealed class ExampleNodeInfoProvider - : InfoProviderBase -{ - public ExampleNodeInfoProvider() - : base(nameof(ExampleNode)) - { - } - - protected override object? GetInfo(ExampleNode obj) - { - return new - { - obj.IsExample, - }; - } -} diff --git a/src/console/LibplanetConsole.Console.Example/ExampleNodeProcessArgumentProvider.cs b/src/console/LibplanetConsole.Console.Example/ExampleNodeProcessArgumentProvider.cs deleted file mode 100644 index 678b6ad4..00000000 --- a/src/console/LibplanetConsole.Console.Example/ExampleNodeProcessArgumentProvider.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace LibplanetConsole.Console.Example; - -internal sealed class ExampleNodeProcessArgumentProvider - : ProcessArgumentProviderBase -{ - protected override IEnumerable GetArguments(ExampleNode obj) - { - if (obj.IsExample == true) - { - yield return "--example"; - } - } -} diff --git a/src/console/LibplanetConsole.Console.Example/ExampleSettings.cs b/src/console/LibplanetConsole.Console.Example/ExampleSettings.cs deleted file mode 100644 index a83eb4fd..00000000 --- a/src/console/LibplanetConsole.Console.Example/ExampleSettings.cs +++ /dev/null @@ -1,12 +0,0 @@ -using JSSoft.Commands; -using LibplanetConsole.Framework; - -namespace LibplanetConsole.Console.Example; - -[ApplicationSettings] -internal sealed class ExampleSettings -{ - [CommandPropertySwitch("node-example")] - [CommandSummary("This is switch for example. not used in real.")] - public bool IsNodeExample { get; init; } -} diff --git a/src/console/LibplanetConsole.Console.Example/IExampleClient.cs b/src/console/LibplanetConsole.Console.Example/IExampleClient.cs deleted file mode 100644 index 6c810cb8..00000000 --- a/src/console/LibplanetConsole.Console.Example/IExampleClient.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace LibplanetConsole.Console.Example; - -public interface IExampleClient -{ - void Subscribe(); - - void Unsubscribe(); -} diff --git a/src/console/LibplanetConsole.Console.Example/IExampleNode.cs b/src/console/LibplanetConsole.Console.Example/IExampleNode.cs deleted file mode 100644 index ba4a230a..00000000 --- a/src/console/LibplanetConsole.Console.Example/IExampleNode.cs +++ /dev/null @@ -1,18 +0,0 @@ -using LibplanetConsole.Common; - -namespace LibplanetConsole.Console.Example; - -public interface IExampleNode -{ - event EventHandler>? Subscribed; - - event EventHandler>? Unsubscribed; - - int Count { get; } - - Task GetAddressesAsync(CancellationToken cancellationToken); - - void Subscribe(Address address); - - void Unsubscribe(Address address); -} diff --git a/src/console/LibplanetConsole.Console.Example/LibplanetConsole.Console.Example.csproj b/src/console/LibplanetConsole.Console.Example/LibplanetConsole.Console.Example.csproj deleted file mode 100644 index 82037c4e..00000000 --- a/src/console/LibplanetConsole.Console.Example/LibplanetConsole.Console.Example.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/console/LibplanetConsole.Console.Example/ServiceCollectionExtensions.cs b/src/console/LibplanetConsole.Console.Example/ServiceCollectionExtensions.cs deleted file mode 100644 index 2428daca..00000000 --- a/src/console/LibplanetConsole.Console.Example/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using JSSoft.Commands; -using LibplanetConsole.Common; -using Microsoft.Extensions.DependencyInjection; - -namespace LibplanetConsole.Console.Example; - -public static class ServiceCollectionExtensions -{ - public static IServiceCollection AddExample(this IServiceCollection @this) - { - @this.AddScoped() - .AddScoped(s => s.GetRequiredService()); - @this.AddScoped() - .AddScoped(s => s.GetRequiredService()); - - @this.AddSingleton(); - @this.AddSingleton(); - - @this.AddSingleton(); - @this.AddSingleton(); - - return @this; - } -} diff --git a/src/console/LibplanetConsole.Console.Executable/LibplanetConsole.Console.Executable.csproj b/src/console/LibplanetConsole.Console.Executable/LibplanetConsole.Console.Executable.csproj index 12ab7c0d..652698a2 100644 --- a/src/console/LibplanetConsole.Console.Executable/LibplanetConsole.Console.Executable.csproj +++ b/src/console/LibplanetConsole.Console.Executable/LibplanetConsole.Console.Executable.csproj @@ -4,8 +4,6 @@ - - diff --git a/src/console/LibplanetConsole.Console.Executable/ServiceCollectionExtensions.cs b/src/console/LibplanetConsole.Console.Executable/ServiceCollectionExtensions.cs index 2a899e52..30902c5e 100644 --- a/src/console/LibplanetConsole.Console.Executable/ServiceCollectionExtensions.cs +++ b/src/console/LibplanetConsole.Console.Executable/ServiceCollectionExtensions.cs @@ -1,6 +1,5 @@ using JSSoft.Commands; using LibplanetConsole.Console.Evidence; -using LibplanetConsole.Console.Example; using LibplanetConsole.Console.Executable.Commands; using LibplanetConsole.Console.Executable.Tracers; using LibplanetConsole.Framework; @@ -30,7 +29,6 @@ public static IServiceCollection AddApplication( @this.AddSingleton(); @this.AddSingleton(); - @this.AddExample(); @this.AddEvidence(); return @this; diff --git a/src/node/LibplanetConsole.Node.Example/Commands/ExampleCommand.cs b/src/node/LibplanetConsole.Node.Example/Commands/ExampleCommand.cs deleted file mode 100644 index 320ea188..00000000 --- a/src/node/LibplanetConsole.Node.Example/Commands/ExampleCommand.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Text; -using JSSoft.Commands; - -namespace LibplanetConsole.Node.Example.Commands; - -[CommandSummary("Example node commands for a quick start.")] -internal sealed class ExampleCommand(IExample sampleNode) - : CommandMethodBase -{ - [CommandMethod] - public void Subscribe(string address) - { - sampleNode.Subscribe(new Address(address)); - } - - [CommandMethod] - public void Unsubscribe(string address) - { - sampleNode.Unsubscribe(new Address(address)); - } - - [CommandMethod] - public void Count() - { - Out.WriteLine(sampleNode.Count); - } - - [CommandMethod] - public async Task ListAsync(CancellationToken cancellationToken) - { - var addresses = await sampleNode.GetAddressesAsync(cancellationToken); - var sb = new StringBuilder(); - foreach (var address in addresses) - { - sb.AppendLine(address.ToString()); - } - - await Out.WriteLineAsync(sb.ToString()); - } -} diff --git a/src/node/LibplanetConsole.Node.Example/Example.cs b/src/node/LibplanetConsole.Node.Example/Example.cs deleted file mode 100644 index 556e118a..00000000 --- a/src/node/LibplanetConsole.Node.Example/Example.cs +++ /dev/null @@ -1,43 +0,0 @@ -using LibplanetConsole.Common; - -namespace LibplanetConsole.Node.Example; - -internal sealed class Example(ExampleSettings settings) : IExample -{ - private readonly HashSet
_addresses = []; - - public event EventHandler>? Subscribed; - - public event EventHandler>? Unsubscribed; - - public int Count => _addresses.Count; - - public bool IsExample { get; } = settings.IsExample; - - public Task GetAddressesAsync(CancellationToken cancellationToken) - => Task.Run(() => _addresses.ToArray(), cancellationToken); - - public void Subscribe(Address address) - { - if (_addresses.Contains(address) == true) - { - throw new ArgumentException("Address is already subscribed.", nameof(address)); - } - - _addresses.Add(address); - Subscribed?.Invoke(this, new ItemEventArgs
(address)); - Console.Out.WriteLine($"Subscribed: '{address}'"); - } - - public void Unsubscribe(Address address) - { - if (_addresses.Contains(address) != true) - { - throw new ArgumentException("Address is not subscribed.", nameof(address)); - } - - _addresses.Remove(address); - Unsubscribed?.Invoke(this, new ItemEventArgs
(address)); - Console.Out.WriteLine($"Unsubscribed: '{address}'"); - } -} diff --git a/src/node/LibplanetConsole.Node.Example/ExampleInfoProvider.cs b/src/node/LibplanetConsole.Node.Example/ExampleInfoProvider.cs deleted file mode 100644 index ec2948e7..00000000 --- a/src/node/LibplanetConsole.Node.Example/ExampleInfoProvider.cs +++ /dev/null @@ -1,15 +0,0 @@ -using LibplanetConsole.Common; - -namespace LibplanetConsole.Node.Example; - -internal sealed class ExampleInfoProvider(Example exampleNode) - : InfoProviderBase(nameof(Example)) -{ - protected override object? GetInfo(IApplication obj) - { - return new - { - exampleNode.IsExample, - }; - } -} diff --git a/src/node/LibplanetConsole.Node.Example/ExampleService.cs b/src/node/LibplanetConsole.Node.Example/ExampleService.cs deleted file mode 100644 index 67533de3..00000000 --- a/src/node/LibplanetConsole.Node.Example/ExampleService.cs +++ /dev/null @@ -1,42 +0,0 @@ -// using LibplanetConsole.Common; -// using LibplanetConsole.Example.Services; - -// namespace LibplanetConsole.Node.Example; - -// internal sealed class ExampleService : LocalService, -// IExampleNodeService, IDisposable -// { -// private readonly Example _example; - -// public ExampleService(Example example) -// { -// _example = example; -// _example.Subscribed += Example_Subscribed; -// _example.Unsubscribed += Example_Unsubscribed; -// } - -// public void Dispose() -// { -// _example.Subscribed -= Example_Subscribed; -// _example.Unsubscribed -= Example_Unsubscribed; -// } - -// public async Task GetAddressCountAsync(CancellationToken cancellationToken) -// { -// var addresses = await _example.GetAddressesAsync(cancellationToken); -// return addresses.Length; -// } - -// public Task GetAddressesAsync(CancellationToken cancellationToken) -// => _example.GetAddressesAsync(cancellationToken); - -// public void Subscribe(Address address) => _example.Subscribe(address); - -// public void Unsubscribe(Address address) => _example.Unsubscribe(address); - -// private void Example_Subscribed(object? sender, ItemEventArgs
e) -// => Callback.OnSubscribed(e.Item); - -// private void Example_Unsubscribed(object? sender, ItemEventArgs
e) -// => Callback.OnUnsubscribed(e.Item); -// } diff --git a/src/node/LibplanetConsole.Node.Example/ExampleSettings.cs b/src/node/LibplanetConsole.Node.Example/ExampleSettings.cs deleted file mode 100644 index 2a11f0fa..00000000 --- a/src/node/LibplanetConsole.Node.Example/ExampleSettings.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.ComponentModel; -using JSSoft.Commands; -using LibplanetConsole.Framework; - -namespace LibplanetConsole.Node.Example; - -[ApplicationSettings] -internal sealed class ExampleSettings -{ - public const string Example = nameof(Example); - - [CommandPropertySwitch("example")] - [CommandSummary("This is switch for example. not used in real.")] - [Category(Example)] - public bool IsExample { get; init; } -} diff --git a/src/node/LibplanetConsole.Node.Example/IExample.cs b/src/node/LibplanetConsole.Node.Example/IExample.cs deleted file mode 100644 index 93e54438..00000000 --- a/src/node/LibplanetConsole.Node.Example/IExample.cs +++ /dev/null @@ -1,20 +0,0 @@ -using LibplanetConsole.Common; - -namespace LibplanetConsole.Node.Example; - -public interface IExample -{ - event EventHandler>? Subscribed; - - event EventHandler>? Unsubscribed; - - int Count { get; } - - bool IsExample { get; } - - Task GetAddressesAsync(CancellationToken cancellationToken); - - void Subscribe(Address address); - - void Unsubscribe(Address address); -} diff --git a/src/node/LibplanetConsole.Node.Example/LibplanetConsole.Node.Example.csproj b/src/node/LibplanetConsole.Node.Example/LibplanetConsole.Node.Example.csproj deleted file mode 100644 index 873adce5..00000000 --- a/src/node/LibplanetConsole.Node.Example/LibplanetConsole.Node.Example.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/node/LibplanetConsole.Node.Example/ServiceCollectionExtensions.cs b/src/node/LibplanetConsole.Node.Example/ServiceCollectionExtensions.cs deleted file mode 100644 index eb0791bd..00000000 --- a/src/node/LibplanetConsole.Node.Example/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using JSSoft.Commands; -using LibplanetConsole.Common; -using LibplanetConsole.Node.Example.Commands; -using Microsoft.Extensions.DependencyInjection; - -namespace LibplanetConsole.Node.Example; - -public static class ServiceCollectionExtensions -{ - public static IServiceCollection AddExample(this IServiceCollection @this) - { - @this.AddSingleton() - .AddSingleton(s => s.GetRequiredService()); - @this.AddSingleton(); - // @this.AddSingleton(); - @this.AddSingleton(); - return @this; - } -} diff --git a/src/node/LibplanetConsole.Node.Executable/LibplanetConsole.Node.Executable.csproj b/src/node/LibplanetConsole.Node.Executable/LibplanetConsole.Node.Executable.csproj index 3998da54..ed3f7fb1 100644 --- a/src/node/LibplanetConsole.Node.Executable/LibplanetConsole.Node.Executable.csproj +++ b/src/node/LibplanetConsole.Node.Executable/LibplanetConsole.Node.Executable.csproj @@ -3,7 +3,6 @@ - diff --git a/src/node/LibplanetConsole.Node.Executable/ServiceCollectionExtensions.cs b/src/node/LibplanetConsole.Node.Executable/ServiceCollectionExtensions.cs index d11562b2..92b4e894 100644 --- a/src/node/LibplanetConsole.Node.Executable/ServiceCollectionExtensions.cs +++ b/src/node/LibplanetConsole.Node.Executable/ServiceCollectionExtensions.cs @@ -2,7 +2,6 @@ using LibplanetConsole.Framework; using LibplanetConsole.Logging; using LibplanetConsole.Node.Evidence; -using LibplanetConsole.Node.Example; using LibplanetConsole.Node.Executable.Commands; using LibplanetConsole.Node.Executable.Tracers; using LibplanetConsole.Node.Explorer; @@ -31,7 +30,6 @@ public static IServiceCollection AddApplication( @this.AddSingleton(); @this.AddEvidence(); - @this.AddExample(); @this.AddGrpc(); diff --git a/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs b/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs index 586c1d12..ef520421 100644 --- a/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs +++ b/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs @@ -1,8 +1,6 @@ using JSSoft.Commands; using LibplanetConsole.Common; -using LibplanetConsole.Framework; using LibplanetConsole.Node.Commands; -using LibplanetConsole.Node.Services; using LibplanetConsole.Seed; using Microsoft.Extensions.DependencyInjection; @@ -20,9 +18,6 @@ public static IServiceCollection AddNode( .AddSingleton(s => s.GetRequiredService()) .AddSingleton(s => s.GetRequiredService()); @this.AddHostedService(); - // @this.AddSingleton(); - // @this.AddSingleton(); - // @this.AddSingleton(); @this.AddSingleton(); @this.AddSingleton(); diff --git a/src/shared/LibplanetConsole.Example/Services/IExampleClientService.cs b/src/shared/LibplanetConsole.Example/Services/IExampleClientService.cs deleted file mode 100644 index 57de9520..00000000 --- a/src/shared/LibplanetConsole.Example/Services/IExampleClientService.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace LibplanetConsole.Example.Services; - -public interface IExampleClientService -{ - void Subscribe(); - - void Unsubscribe(); -} diff --git a/src/shared/LibplanetConsole.Example/Services/IExampleNodeCallback.cs b/src/shared/LibplanetConsole.Example/Services/IExampleNodeCallback.cs deleted file mode 100644 index d688c09e..00000000 --- a/src/shared/LibplanetConsole.Example/Services/IExampleNodeCallback.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace LibplanetConsole.Example.Services; - -public interface IExampleNodeCallback -{ - void OnSubscribed(Address address); - - void OnUnsubscribed(Address address); -} diff --git a/src/shared/LibplanetConsole.Example/Services/IExampleNodeService.cs b/src/shared/LibplanetConsole.Example/Services/IExampleNodeService.cs deleted file mode 100644 index 6fcfe7ac..00000000 --- a/src/shared/LibplanetConsole.Example/Services/IExampleNodeService.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace LibplanetConsole.Example.Services; - -public interface IExampleNodeService -{ - void Subscribe(Address address); - - void Unsubscribe(Address address); - - Task GetAddressCountAsync(CancellationToken cancellationToken); - - Task GetAddressesAsync(CancellationToken cancellationToken); -} From 33e8c2eb7077413e36db45fd4f74402aad43025d Mon Sep 17 00:00:00 2001 From: s2quake Date: Sat, 5 Oct 2024 21:26:48 +0900 Subject: [PATCH 04/18] refactor: Remove application from node project --- .gitmodules | 3 + .submodules/commands | 1 + .../Application.cs | 128 ++++++++-------- .../EntryCommands/RunCommand.cs | 20 +-- .../EntryCommands/StartCommand.cs | 10 +- .../ServiceCollectionExtensions.cs | 9 +- .../SystemTerminal.cs | 5 +- .../TerminalHostedService.cs | 81 ++++++++++ .../Tracers/BlockChainEventTracer.cs | 26 ++-- .../Tracers/NodeEventTracer.cs | 30 ++-- .../LibplanetConsole.Node/ApplicationBase.cs | 144 +++++++++--------- .../ApplicationInfoProvider.cs | 20 +-- .../Commands/ExitCommand.cs | 15 +- .../Commands/InfoCommand.cs | 26 ++-- .../LibplanetConsole.Node/IApplication.cs | 16 +- .../LibplanetConsole.Node/NodeInfoProvider.cs | 12 +- src/node/LibplanetConsole.Node/SeedService.cs | 5 +- .../ServiceCollectionExtensions.cs | 13 +- 18 files changed, 340 insertions(+), 224 deletions(-) create mode 160000 .submodules/commands create mode 100644 src/node/LibplanetConsole.Node.Executable/TerminalHostedService.cs diff --git a/.gitmodules b/.gitmodules index e69de29b..41a3b656 100644 --- a/.gitmodules +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule ".submodules/commands"] + path = .submodules/commands + url = https://github.com/s2quake/commands.git diff --git a/.submodules/commands b/.submodules/commands new file mode 160000 index 00000000..71229037 --- /dev/null +++ b/.submodules/commands @@ -0,0 +1 @@ +Subproject commit 712290379a38ae88ecded8954c3f17d3d54010a4 diff --git a/src/node/LibplanetConsole.Node.Executable/Application.cs b/src/node/LibplanetConsole.Node.Executable/Application.cs index 6a77c3fa..92b204a9 100644 --- a/src/node/LibplanetConsole.Node.Executable/Application.cs +++ b/src/node/LibplanetConsole.Node.Executable/Application.cs @@ -1,73 +1,73 @@ -using JSSoft.Commands.Extensions; -using JSSoft.Terminals; -using LibplanetConsole.Common.Extensions; -using Microsoft.Extensions.DependencyInjection; +// using JSSoft.Commands.Extensions; +// using JSSoft.Terminals; +// using LibplanetConsole.Common.Extensions; +// using Microsoft.Extensions.DependencyInjection; -namespace LibplanetConsole.Node.Executable; +// namespace LibplanetConsole.Node.Executable; -internal sealed class Application(IServiceProvider serviceProvider, ApplicationOptions options) - : ApplicationBase(serviceProvider, options), IApplication -{ - private readonly ApplicationOptions _options = options; - private Task _waitInputTask = Task.CompletedTask; +// internal sealed class Application(IServiceProvider serviceProvider, ApplicationOptions options) +// : ApplicationBase(serviceProvider, options), IApplication +// { +// private readonly ApplicationOptions _options = options; +// private Task _waitInputTask = Task.CompletedTask; - protected override async Task OnRunAsync(CancellationToken cancellationToken) - { - if (_options.NoREPL != true) - { - var sw = new StringWriter(); - var commandContext = this.GetRequiredService(); - var terminal = this.GetRequiredService(); - commandContext.Out = sw; - await base.OnRunAsync(cancellationToken); - await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); - await commandContext.ExecuteAsync(["--help"], cancellationToken: default); - await sw.WriteLineAsync(); - await commandContext.ExecuteAsync(args: [], cancellationToken: default); - await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); - commandContext.Out = Console.Out; - await sw.WriteLineIfAsync(GetStartupCondition(_options), GetStartupMessage()); - await Console.Out.WriteAsync(sw.ToString()); +// protected override async Task OnRunAsync(CancellationToken cancellationToken) +// { +// if (_options.NoREPL != true) +// { +// var sw = new StringWriter(); +// var commandContext = this.GetRequiredService(); +// var terminal = this.GetRequiredService(); +// commandContext.Out = sw; +// await base.OnRunAsync(cancellationToken); +// await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); +// await commandContext.ExecuteAsync(["--help"], cancellationToken: default); +// await sw.WriteLineAsync(); +// await commandContext.ExecuteAsync(args: [], cancellationToken: default); +// await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); +// commandContext.Out = Console.Out; +// await sw.WriteLineIfAsync(GetStartupCondition(_options), GetStartupMessage()); +// await Console.Out.WriteAsync(sw.ToString()); - await terminal.StartAsync(cancellationToken); - } - else if (_options.ParentProcessId == 0) - { - await base.OnRunAsync(cancellationToken); - _waitInputTask = WaitInputAsync(); - } - else - { - await base.OnRunAsync(cancellationToken); - } - } +// await terminal.StartAsync(cancellationToken); +// } +// else if (_options.ParentProcessId == 0) +// { +// await base.OnRunAsync(cancellationToken); +// _waitInputTask = WaitInputAsync(); +// } +// else +// { +// await base.OnRunAsync(cancellationToken); +// } +// } - protected override async ValueTask OnDisposeAsync() - { - await base.OnDisposeAsync(); - await _waitInputTask; - } +// protected override async ValueTask OnDisposeAsync() +// { +// await base.OnDisposeAsync(); +// await _waitInputTask; +// } - private static bool GetStartupCondition(ApplicationOptions options) - { - if (options.SeedEndPoint is not null) - { - return false; - } +// private static bool GetStartupCondition(ApplicationOptions options) +// { +// if (options.SeedEndPoint is not null) +// { +// return false; +// } - return options.ParentProcessId == 0; - } +// return options.ParentProcessId == 0; +// } - private static string GetStartupMessage() - { - var startText = TerminalStringBuilder.GetString("start", TerminalColorType.Green); - return $"\nType '{startText}' to start the node."; - } +// private static string GetStartupMessage() +// { +// var startText = TerminalStringBuilder.GetString("start", TerminalColorType.Green); +// return $"\nType '{startText}' to start the node."; +// } - private async Task WaitInputAsync() - { - await Console.Out.WriteLineAsync("Press any key to exit."); - await Task.Run(() => Console.ReadKey(intercept: true)); - Cancel(); - } -} +// private async Task WaitInputAsync() +// { +// await Console.Out.WriteLineAsync("Press any key to exit."); +// await Task.Run(() => Console.ReadKey(intercept: true)); +// Cancel(); +// } +// } diff --git a/src/node/LibplanetConsole.Node.Executable/EntryCommands/RunCommand.cs b/src/node/LibplanetConsole.Node.Executable/EntryCommands/RunCommand.cs index b34aaff7..e6433667 100644 --- a/src/node/LibplanetConsole.Node.Executable/EntryCommands/RunCommand.cs +++ b/src/node/LibplanetConsole.Node.Executable/EntryCommands/RunCommand.cs @@ -28,18 +28,18 @@ protected override async Task OnExecuteAsync(CancellationToken cancellationToken { try { - var serviceCollection = new ApplicationServiceCollection(_settingsCollection); - var applicationOptions = _applicationSettings.ToOptions(); + // var serviceCollection = new ApplicationServiceCollection(_settingsCollection); + // var applicationOptions = _applicationSettings.ToOptions(); - serviceCollection.AddNode(applicationOptions); - serviceCollection.AddApplication(applicationOptions); + // serviceCollection.AddNode(applicationOptions); + // serviceCollection.AddApplication(applicationOptions); - await using var serviceProvider = serviceCollection.BuildServiceProvider(); - var @out = Console.Out; - var application = serviceProvider.GetRequiredService(); - await @out.WriteLineAsync(); - await application.RunAsync(); - await @out.WriteLineAsync("\u001b0"); + // await using var serviceProvider = serviceCollection.BuildServiceProvider(); + // var @out = Console.Out; + // var application = serviceProvider.GetRequiredService(); + // await @out.WriteLineAsync(); + // await application.RunAsync(); + // await @out.WriteLineAsync("\u001b0"); } catch (CommandParsingException e) { diff --git a/src/node/LibplanetConsole.Node.Executable/EntryCommands/StartCommand.cs b/src/node/LibplanetConsole.Node.Executable/EntryCommands/StartCommand.cs index e1114699..53e8a497 100644 --- a/src/node/LibplanetConsole.Node.Executable/EntryCommands/StartCommand.cs +++ b/src/node/LibplanetConsole.Node.Executable/EntryCommands/StartCommand.cs @@ -77,12 +77,12 @@ protected override async Task OnExecuteAsync(CancellationToken cancellationToken app.UseAuthorization(); var @out = Console.Out; - await app.StartAsync(cancellationToken); - var application = app.Services.GetRequiredService(); - await @out.WriteLineAsync(); - await application.RunAsync(); + await app.RunAsync(cancellationToken); + // var application = app.Services.GetRequiredService(); + // await @out.WriteLineAsync(); + // await application.RunAsync(); await @out.WriteLineAsync("\u001b0"); - await app.StopAsync(cancellationToken); + // await app.StopAsync(cancellationToken); } catch (CommandParsingException e) { diff --git a/src/node/LibplanetConsole.Node.Executable/ServiceCollectionExtensions.cs b/src/node/LibplanetConsole.Node.Executable/ServiceCollectionExtensions.cs index 92b4e894..2a454b1b 100644 --- a/src/node/LibplanetConsole.Node.Executable/ServiceCollectionExtensions.cs +++ b/src/node/LibplanetConsole.Node.Executable/ServiceCollectionExtensions.cs @@ -15,19 +15,20 @@ public static IServiceCollection AddApplication( { @this.AddLogging(options.LogPath, options.LibraryLogPath); - @this.AddSingleton(s => new Application(s, options)); - @this.AddSingleton(s => s.GetRequiredService()); + // @this.AddSingleton(s => new Application(s, options)); + // @this.AddSingleton(s => s.GetRequiredService()); @this.AddSingleton(); @this.AddSingleton(); + @this.AddHostedService(); @this.AddSingleton() .AddSingleton(s => s.GetRequiredService()); @this.AddSingleton() .AddSingleton(s => s.GetRequiredService()); - @this.AddSingleton(); - @this.AddSingleton(); + @this.AddHostedService(); + @this.AddHostedService(); @this.AddEvidence(); diff --git a/src/node/LibplanetConsole.Node.Executable/SystemTerminal.cs b/src/node/LibplanetConsole.Node.Executable/SystemTerminal.cs index 65d7abf5..f1452779 100644 --- a/src/node/LibplanetConsole.Node.Executable/SystemTerminal.cs +++ b/src/node/LibplanetConsole.Node.Executable/SystemTerminal.cs @@ -7,10 +7,11 @@ internal sealed class SystemTerminal : SystemTerminalBase { private readonly CommandContext _commandContext; - public SystemTerminal(IApplication application, CommandContext commandContext) + public SystemTerminal( + IHostApplicationLifetime applicationLifetime, CommandContext commandContext) { _commandContext = commandContext; - _commandContext.Owner = application; + _commandContext.Owner = applicationLifetime; Prompt = "libplanet-node $ "; } diff --git a/src/node/LibplanetConsole.Node.Executable/TerminalHostedService.cs b/src/node/LibplanetConsole.Node.Executable/TerminalHostedService.cs new file mode 100644 index 00000000..73264166 --- /dev/null +++ b/src/node/LibplanetConsole.Node.Executable/TerminalHostedService.cs @@ -0,0 +1,81 @@ +using JSSoft.Commands.Extensions; +using JSSoft.Terminals; +using LibplanetConsole.Common.Extensions; +using Microsoft.Extensions.DependencyInjection; + +namespace LibplanetConsole.Node.Executable; + +internal sealed class TerminalHostedService( + IHostApplicationLifetime applicationLifetime, + CommandContext commandContext, + SystemTerminal terminal, + ApplicationOptions _options) : IHostedService +{ + private Task _runningTask = Task.CompletedTask; + + public async Task StartAsync(CancellationToken cancellationToken) + { + if (_options.NoREPL != true) + { + applicationLifetime.ApplicationStarted.Register(async () => + { + var sw = new StringWriter(); + commandContext.Out = sw; + // await base.OnRunAsync(cancellationToken); + await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); + await commandContext.ExecuteAsync(["--help"], cancellationToken: default); + await sw.WriteLineAsync(); + await commandContext.ExecuteAsync(args: [], cancellationToken: default); + await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); + commandContext.Out = Console.Out; + // await sw.WriteLineIfAsync(GetStartupCondition(_options), GetStartupMessage()); + await Console.Out.WriteAsync(sw.ToString()); + + _runningTask = terminal.StartAsync(cancellationToken); + }); + } + else if (_options.ParentProcessId == 0) + { + // _waitInputTask = WaitInputAsync(); + } + else + { + // await base.OnRunAsync(cancellationToken); + } + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + await _runningTask; + await terminal.StopAsync(cancellationToken); + } + + // protected override async ValueTask OnDisposeAsync() + // { + // await base.OnDisposeAsync(); + // await _waitInputTask; + // } + + // private static bool GetStartupCondition(ApplicationOptions options) + // { + // if (options.SeedEndPoint is not null) + // { + // return false; + // } + + // return options.ParentProcessId == 0; + // } + + // private static string GetStartupMessage() + // { + // var startText = TerminalStringBuilder.GetString("start", TerminalColorType.Green); + // return $"\nType '{startText}' to start the node."; + // } + + // private async Task WaitInputAsync() + // { + // await Console.Out.WriteLineAsync("Press any key to exit."); + // await Task.Run(() => Console.ReadKey(intercept: true)); + // Cancel(); + // } +} diff --git a/src/node/LibplanetConsole.Node.Executable/Tracers/BlockChainEventTracer.cs b/src/node/LibplanetConsole.Node.Executable/Tracers/BlockChainEventTracer.cs index 37ed44e8..c4ebaed4 100644 --- a/src/node/LibplanetConsole.Node.Executable/Tracers/BlockChainEventTracer.cs +++ b/src/node/LibplanetConsole.Node.Executable/Tracers/BlockChainEventTracer.cs @@ -1,22 +1,30 @@ using JSSoft.Terminals; using LibplanetConsole.Common.Extensions; -using LibplanetConsole.Framework; namespace LibplanetConsole.Node.Executable.Tracers; -internal sealed class BlockChainEventTracer( - IBlockChain blockChain, ILogger logger) - : IApplicationService, IDisposable +internal sealed class BlockChainEventTracer : IHostedService, IDisposable { - public Task InitializeAsync(CancellationToken cancellationToken) + private readonly IBlockChain _blockChain; + private readonly ILogger _logger; + + public BlockChainEventTracer( + IBlockChain blockChain, ILogger logger) { - blockChain.BlockAppended += Node_BlockAppended; - return Task.CompletedTask; + _blockChain = blockChain; + _logger = logger; + _blockChain.BlockAppended += Node_BlockAppended; } + public Task StartAsync(CancellationToken cancellationToken) + => Task.CompletedTask; + + public Task StopAsync(CancellationToken cancellationToken) + => Task.CompletedTask; + void IDisposable.Dispose() { - blockChain.BlockAppended -= Node_BlockAppended; + _blockChain.BlockAppended -= Node_BlockAppended; } private void Node_BlockAppended(object? sender, BlockEventArgs e) @@ -27,6 +35,6 @@ private void Node_BlockAppended(object? sender, BlockEventArgs e) var message = $"Block #{blockInfo.Height} '{hash.ToShortString()}' " + $"Appended by '{miner.ToShortString()}'"; Console.Out.WriteColoredLine(message, TerminalColorType.BrightGreen); - logger.LogInformation(message); + _logger.LogInformation(message); } } diff --git a/src/node/LibplanetConsole.Node.Executable/Tracers/NodeEventTracer.cs b/src/node/LibplanetConsole.Node.Executable/Tracers/NodeEventTracer.cs index 0eb09d95..a5c2f73f 100644 --- a/src/node/LibplanetConsole.Node.Executable/Tracers/NodeEventTracer.cs +++ b/src/node/LibplanetConsole.Node.Executable/Tracers/NodeEventTracer.cs @@ -1,35 +1,43 @@ using JSSoft.Terminals; using LibplanetConsole.Common.Extensions; -using LibplanetConsole.Framework; namespace LibplanetConsole.Node.Executable.Tracers; -internal sealed class NodeEventTracer(IApplication application, INode node) - : IApplicationService, IDisposable +internal sealed class NodeEventTracer : IHostedService, IDisposable { - public Task InitializeAsync(CancellationToken cancellationToken) + private readonly ApplicationOptions _options; + private readonly INode _node; + + public NodeEventTracer(ApplicationOptions options, INode node) { - node.Started += Node_Started; - node.Stopped += Node_Stopped; - return Task.CompletedTask; + _options = options; + _node = node; + _node.Started += Node_Started; + _node.Stopped += Node_Stopped; } + public Task StartAsync(CancellationToken cancellationToken) + => Task.CompletedTask; + + public Task StopAsync(CancellationToken cancellationToken) + => Task.CompletedTask; + void IDisposable.Dispose() { - node.Started -= Node_Started; - node.Stopped -= Node_Stopped; + _node.Started -= Node_Started; + _node.Stopped -= Node_Stopped; } private void Node_Started(object? sender, EventArgs e) { - var endPoint = application.Info.EndPoint; + var endPoint = _options.EndPoint; var message = $"BlockChain has been started.: {endPoint}"; Console.Out.WriteColoredLine(message, TerminalColorType.BrightGreen); } private void Node_Stopped(object? sender, EventArgs e) { - var endPoint = application.Info.EndPoint; + var endPoint = _options.EndPoint; var message = $"BlockChain has been stopped.: {endPoint}"; Console.Out.WriteColoredLine(message, TerminalColorType.BrightGreen); } diff --git a/src/node/LibplanetConsole.Node/ApplicationBase.cs b/src/node/LibplanetConsole.Node/ApplicationBase.cs index bd79ac2f..3aa95a4a 100644 --- a/src/node/LibplanetConsole.Node/ApplicationBase.cs +++ b/src/node/LibplanetConsole.Node/ApplicationBase.cs @@ -1,83 +1,83 @@ -using System.Diagnostics; -using LibplanetConsole.Common.Extensions; -using LibplanetConsole.Framework; -using LibplanetConsole.Node.Services; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; +// using System.Diagnostics; +// using LibplanetConsole.Common.Extensions; +// using LibplanetConsole.Framework; +// using LibplanetConsole.Node.Services; +// using Microsoft.Extensions.DependencyInjection; +// using Microsoft.Extensions.Logging; -namespace LibplanetConsole.Node; +// namespace LibplanetConsole.Node; -public abstract class ApplicationBase : ApplicationFramework, IApplication -{ - private readonly IServiceProvider _serviceProvider; - private readonly Node _node; - private readonly Process? _parentProcess; - private readonly ILogger _logger; - private readonly ApplicationInfo _info; - private readonly Task _waitForExitTask = Task.CompletedTask; - // private NodeContext? _nodeContext; - private Guid _closeToken; +// public abstract class ApplicationBase : ApplicationFramework, IApplication +// { +// private readonly IServiceProvider _serviceProvider; +// private readonly Node _node; +// private readonly Process? _parentProcess; +// private readonly ILogger _logger; +// private readonly ApplicationInfo _info; +// private readonly Task _waitForExitTask = Task.CompletedTask; +// // private NodeContext? _nodeContext; +// private Guid _closeToken; - protected ApplicationBase(IServiceProvider serviceProvider, ApplicationOptions options) - : base(serviceProvider) - { - _serviceProvider = serviceProvider; - _logger = serviceProvider.GetLogger(); - _logger.LogDebug(Environment.CommandLine); - _logger.LogDebug("Application initializing..."); - _node = serviceProvider.GetRequiredService(); - _info = new() - { - EndPoint = options.EndPoint, - SeedEndPoint = options.SeedEndPoint, - StorePath = options.StorePath, - LogPath = options.LogPath, - ParentProcessId = options.ParentProcessId, - }; - if (options.ParentProcessId != 0 && - Process.GetProcessById(options.ParentProcessId) is { } parentProcess) - { - _parentProcess = parentProcess; - _waitForExitTask = WaitForExit(parentProcess, Cancel); - } +// protected ApplicationBase(IServiceProvider serviceProvider, ApplicationOptions options) +// : base(serviceProvider) +// { +// _serviceProvider = serviceProvider; +// _logger = serviceProvider.GetLogger(); +// _logger.LogDebug(Environment.CommandLine); +// _logger.LogDebug("Application initializing..."); +// _node = serviceProvider.GetRequiredService(); +// _info = new() +// { +// EndPoint = options.EndPoint, +// SeedEndPoint = options.SeedEndPoint, +// StorePath = options.StorePath, +// LogPath = options.LogPath, +// ParentProcessId = options.ParentProcessId, +// }; +// if (options.ParentProcessId != 0 && +// Process.GetProcessById(options.ParentProcessId) is { } parentProcess) +// { +// _parentProcess = parentProcess; +// _waitForExitTask = WaitForExit(parentProcess, Cancel); +// } - _logger.LogDebug("Application initialized."); - } +// _logger.LogDebug("Application initialized."); +// } - public ApplicationInfo Info => _info; +// public ApplicationInfo Info => _info; - protected override bool CanClose => _parentProcess?.HasExited == true; +// protected override bool CanClose => _parentProcess?.HasExited == true; - public override object? GetService(Type serviceType) - => _serviceProvider.GetService(serviceType); +// public override object? GetService(Type serviceType) +// => _serviceProvider.GetService(serviceType); - protected override async Task OnRunAsync(CancellationToken cancellationToken) - { - _logger.LogDebug("NodeContext is starting: {EndPoint}", _info.EndPoint); - // _nodeContext = _serviceProvider.GetRequiredService(); - // _nodeContext.EndPoint = _info.EndPoint; - // _closeToken = await _nodeContext.StartAsync(cancellationToken: default); - _logger.LogDebug("NodeContext is started: {EndPoint}", _info.EndPoint); - await base.OnRunAsync(cancellationToken); - } +// protected override async Task OnRunAsync(CancellationToken cancellationToken) +// { +// _logger.LogDebug("NodeContext is starting: {EndPoint}", _info.EndPoint); +// // _nodeContext = _serviceProvider.GetRequiredService(); +// // _nodeContext.EndPoint = _info.EndPoint; +// // _closeToken = await _nodeContext.StartAsync(cancellationToken: default); +// _logger.LogDebug("NodeContext is started: {EndPoint}", _info.EndPoint); +// await base.OnRunAsync(cancellationToken); +// } - protected override async ValueTask OnDisposeAsync() - { - await base.OnDisposeAsync(); - // if (_nodeContext is not null) - // { - // _logger.LogDebug("NodeContext is closing: {EndPoint}", _info.EndPoint); - // await _nodeContext.CloseAsync(_closeToken, cancellationToken: default); - // _nodeContext = null; - // _logger.LogDebug("NodeContext is closed: {EndPoint}", _info.EndPoint); - // } +// protected override async ValueTask OnDisposeAsync() +// { +// await base.OnDisposeAsync(); +// // if (_nodeContext is not null) +// // { +// // _logger.LogDebug("NodeContext is closing: {EndPoint}", _info.EndPoint); +// // await _nodeContext.CloseAsync(_closeToken, cancellationToken: default); +// // _nodeContext = null; +// // _logger.LogDebug("NodeContext is closed: {EndPoint}", _info.EndPoint); +// // } - await _waitForExitTask; - } +// await _waitForExitTask; +// } - private static async Task WaitForExit(Process process, Action cancelAction) - { - await process.WaitForExitAsync(); - cancelAction.Invoke(); - } -} +// private static async Task WaitForExit(Process process, Action cancelAction) +// { +// await process.WaitForExitAsync(); +// cancelAction.Invoke(); +// } +// } diff --git a/src/node/LibplanetConsole.Node/ApplicationInfoProvider.cs b/src/node/LibplanetConsole.Node/ApplicationInfoProvider.cs index 059c7faa..7290644e 100644 --- a/src/node/LibplanetConsole.Node/ApplicationInfoProvider.cs +++ b/src/node/LibplanetConsole.Node/ApplicationInfoProvider.cs @@ -1,13 +1,13 @@ -using LibplanetConsole.Common; +// using LibplanetConsole.Common; -namespace LibplanetConsole.Node; +// namespace LibplanetConsole.Node; -internal sealed class ApplicationInfoProvider : InfoProviderBase -{ - public ApplicationInfoProvider() - : base("Application") - { - } +// internal sealed class ApplicationInfoProvider : InfoProviderBase +// { +// public ApplicationInfoProvider() +// : base("Application") +// { +// } - protected override object? GetInfo(ApplicationBase obj) => obj.Info; -} +// protected override object? GetInfo(ApplicationBase obj) => obj.Info; +// } diff --git a/src/node/LibplanetConsole.Node/Commands/ExitCommand.cs b/src/node/LibplanetConsole.Node/Commands/ExitCommand.cs index 907c42c0..ef31c253 100644 --- a/src/node/LibplanetConsole.Node/Commands/ExitCommand.cs +++ b/src/node/LibplanetConsole.Node/Commands/ExitCommand.cs @@ -1,9 +1,20 @@ using JSSoft.Commands; +using Microsoft.Extensions.Hosting; namespace LibplanetConsole.Node.Commands; [CommandSummary("Exit the application.")] -internal sealed class ExitCommand(IApplication application) : CommandBase +internal sealed class ExitCommand(IHostApplicationLifetime applicationLifetime) + : CommandAsyncBase { - protected override void OnExecute() => application.Cancel(); + protected override async Task OnExecuteAsync(CancellationToken cancellationToken) + { + var resetEvent = new ManualResetEvent(false); + applicationLifetime.ApplicationStopped.Register(() => resetEvent.Set()); + applicationLifetime.StopApplication(); + while (!resetEvent.WaitOne(100)) + { + await Task.Yield(); + } + } } diff --git a/src/node/LibplanetConsole.Node/Commands/InfoCommand.cs b/src/node/LibplanetConsole.Node/Commands/InfoCommand.cs index ca45352c..4f1e07c1 100644 --- a/src/node/LibplanetConsole.Node/Commands/InfoCommand.cs +++ b/src/node/LibplanetConsole.Node/Commands/InfoCommand.cs @@ -1,15 +1,15 @@ -using JSSoft.Commands; -using LibplanetConsole.Common; -using LibplanetConsole.Common.Extensions; +// using JSSoft.Commands; +// using LibplanetConsole.Common; +// using LibplanetConsole.Common.Extensions; -namespace LibplanetConsole.Node.Commands; +// namespace LibplanetConsole.Node.Commands; -[CommandSummary("Print node application information.")] -internal sealed class InfoCommand(IApplication application) : CommandBase -{ - protected override void OnExecute() - { - var info = InfoUtility.GetInfo(serviceProvider: application, obj: application); - Out.WriteLineAsJson(info); - } -} +// [CommandSummary("Print node application information.")] +// internal sealed class InfoCommand(IApplication application) : CommandBase +// { +// protected override void OnExecute() +// { +// var info = InfoUtility.GetInfo(serviceProvider: application, obj: application); +// Out.WriteLineAsJson(info); +// } +// } diff --git a/src/node/LibplanetConsole.Node/IApplication.cs b/src/node/LibplanetConsole.Node/IApplication.cs index 8c844f53..9b82ffdb 100644 --- a/src/node/LibplanetConsole.Node/IApplication.cs +++ b/src/node/LibplanetConsole.Node/IApplication.cs @@ -1,12 +1,12 @@ -namespace LibplanetConsole.Node; +// namespace LibplanetConsole.Node; -public interface IApplication : IAsyncDisposable, IServiceProvider -{ - ApplicationInfo Info { get; } +// public interface IApplication : IAsyncDisposable, IServiceProvider +// { +// ApplicationInfo Info { get; } - Task InvokeAsync(Action action, CancellationToken cancellationToken); +// Task InvokeAsync(Action action, CancellationToken cancellationToken); - Task InvokeAsync(Func func, CancellationToken cancellationToken); +// Task InvokeAsync(Func func, CancellationToken cancellationToken); - void Cancel(); -} +// void Cancel(); +// } diff --git a/src/node/LibplanetConsole.Node/NodeInfoProvider.cs b/src/node/LibplanetConsole.Node/NodeInfoProvider.cs index 3e1a760b..36df5415 100644 --- a/src/node/LibplanetConsole.Node/NodeInfoProvider.cs +++ b/src/node/LibplanetConsole.Node/NodeInfoProvider.cs @@ -1,8 +1,8 @@ -using LibplanetConsole.Common; +// using LibplanetConsole.Common; -namespace LibplanetConsole.Node; +// namespace LibplanetConsole.Node; -internal sealed class NodeInfoProvider(Node node) : InfoProviderBase(nameof(Node)) -{ - protected override object? GetInfo(ApplicationBase obj) => node.Info; -} +// internal sealed class NodeInfoProvider(Node node) : InfoProviderBase(nameof(Node)) +// { +// protected override object? GetInfo(ApplicationBase obj) => node.Info; +// } diff --git a/src/node/LibplanetConsole.Node/SeedService.cs b/src/node/LibplanetConsole.Node/SeedService.cs index f12f22c6..8687d6d3 100644 --- a/src/node/LibplanetConsole.Node/SeedService.cs +++ b/src/node/LibplanetConsole.Node/SeedService.cs @@ -4,7 +4,7 @@ namespace LibplanetConsole.Node; -internal sealed class SeedService(IApplication application) : ISeedService, IHostedService +internal sealed class SeedService(ApplicationOptions options) : ISeedService, IHostedService { private readonly PrivateKey _seedNodePrivateKey = new(); private SeedNode? _blocksyncSeedNode; @@ -31,8 +31,7 @@ public Task GetSeedAsync( async Task IHostedService.StartAsync(CancellationToken cancellationToken) { - var info = application.Info; - if (CompareEndPoint(info.SeedEndPoint, info.EndPoint) is true) + if (CompareEndPoint(options.SeedEndPoint, options.EndPoint) is true) { _blocksyncSeedNode = new SeedNode(new() { diff --git a/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs b/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs index ef520421..58e45ecd 100644 --- a/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs +++ b/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs @@ -1,5 +1,4 @@ using JSSoft.Commands; -using LibplanetConsole.Common; using LibplanetConsole.Node.Commands; using LibplanetConsole.Seed; using Microsoft.Extensions.DependencyInjection; @@ -11,19 +10,23 @@ public static class ServiceCollectionExtensions public static IServiceCollection AddNode( this IServiceCollection @this, ApplicationOptions options) { + SynchronizationContext.SetSynchronizationContext(new()); + @this.AddSingleton(SynchronizationContext.Current); + @this.AddSingleton(options); @this.AddSingleton() .AddSingleton(s => s.GetRequiredService()) .AddHostedService(s => s.GetRequiredService()); - @this.AddSingleton(s => new Node(s, options)) + @this.AddSingleton() .AddSingleton(s => s.GetRequiredService()) .AddSingleton(s => s.GetRequiredService()); @this.AddHostedService(); - @this.AddSingleton(); - @this.AddSingleton(); + // @this.AddSingleton(); + // @this.AddSingleton(); + @this.AddSingleton(); @this.AddSingleton(); - @this.AddSingleton(); + // @this.AddSingleton(); @this.AddSingleton(); @this.AddSingleton(); @this.AddSingleton(); From 2bbc42e353719cb02dd410e00f4831206e46cfa9 Mon Sep 17 00:00:00 2001 From: s2quake Date: Sun, 6 Oct 2024 08:34:25 +0900 Subject: [PATCH 05/18] refactor: Evidence, Exit/InfoCommand --- .gitmodules | 3 - .submodules/commands | 1 - Directory.Build.props | 2 +- .../Evidence.cs | 1 - .../LibplanetConsole.Console.Evidence.csproj | 5 ++ .../LibplanetConsole.Node.Evidence.csproj | 2 + .../Services/EvidenceService.cs | 29 -------- .../Services/EvidenceServiceGrpcV1.cs | 39 ++++++++++ .../EntryCommands/StartCommand.cs | 6 +- .../ServiceCollectionExtensions.cs | 3 - .../SystemTerminal.cs | 1 + .../TerminalHostedService.cs | 71 +++++++++---------- .../ApplicationInfoProvider.cs | 32 ++++++--- .../Commands/ExitCommand.cs | 9 ++- .../Commands/InfoCommand.cs | 29 ++++---- .../LibplanetConsole.Node/IApplication.cs | 12 ---- .../LibplanetConsole.Node/IGenesisCreator.cs | 6 -- .../LibplanetConsole.Node/NodeInfoProvider.cs | 14 ++-- .../ServiceCollectionExtensions.cs | 13 ++-- .../LibplanetConsole.Evidence/EvidenceInfo.cs | 26 +++++++ .../Protos/EvidenceGrpcService.proto | 45 ++++++++++++ .../Services/IEvidenceService.cs | 14 ---- .../Services/IBlockChainService.cs | 40 +++++------ .../Services/INodeService.cs | 14 ++-- 24 files changed, 241 insertions(+), 176 deletions(-) delete mode 160000 .submodules/commands delete mode 100644 src/node/LibplanetConsole.Node.Evidence/Services/EvidenceService.cs create mode 100644 src/node/LibplanetConsole.Node.Evidence/Services/EvidenceServiceGrpcV1.cs delete mode 100644 src/node/LibplanetConsole.Node/IApplication.cs delete mode 100644 src/node/LibplanetConsole.Node/IGenesisCreator.cs create mode 100644 src/shared/LibplanetConsole.Evidence/Protos/EvidenceGrpcService.proto delete mode 100644 src/shared/LibplanetConsole.Evidence/Services/IEvidenceService.cs diff --git a/.gitmodules b/.gitmodules index 41a3b656..e69de29b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule ".submodules/commands"] - path = .submodules/commands - url = https://github.com/s2quake/commands.git diff --git a/.submodules/commands b/.submodules/commands deleted file mode 160000 index 71229037..00000000 --- a/.submodules/commands +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 712290379a38ae88ecded8954c3f17d3d54010a4 diff --git a/Directory.Build.props b/Directory.Build.props index be5eacf5..be01849f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -35,7 +35,7 @@ $(SolutionDir) $(MSBuildThisFileDirectory)\ 5.2.0 - 7.0.0-pr.40 + 7.0.0-pr.42 2.0.6 1.0.0 true diff --git a/src/console/LibplanetConsole.Console.Evidence/Evidence.cs b/src/console/LibplanetConsole.Console.Evidence/Evidence.cs index 3f602fb8..7c861e7d 100644 --- a/src/console/LibplanetConsole.Console.Evidence/Evidence.cs +++ b/src/console/LibplanetConsole.Console.Evidence/Evidence.cs @@ -1,5 +1,4 @@ using LibplanetConsole.Evidence; -using LibplanetConsole.Evidence.Services; namespace LibplanetConsole.Console.Evidence; diff --git a/src/console/LibplanetConsole.Console.Evidence/LibplanetConsole.Console.Evidence.csproj b/src/console/LibplanetConsole.Console.Evidence/LibplanetConsole.Console.Evidence.csproj index a5d68453..dcb52f72 100644 --- a/src/console/LibplanetConsole.Console.Evidence/LibplanetConsole.Console.Evidence.csproj +++ b/src/console/LibplanetConsole.Console.Evidence/LibplanetConsole.Console.Evidence.csproj @@ -6,6 +6,11 @@ + + + + + diff --git a/src/node/LibplanetConsole.Node.Evidence/LibplanetConsole.Node.Evidence.csproj b/src/node/LibplanetConsole.Node.Evidence/LibplanetConsole.Node.Evidence.csproj index 297e3388..80e746bc 100644 --- a/src/node/LibplanetConsole.Node.Evidence/LibplanetConsole.Node.Evidence.csproj +++ b/src/node/LibplanetConsole.Node.Evidence/LibplanetConsole.Node.Evidence.csproj @@ -5,7 +5,9 @@ + + diff --git a/src/node/LibplanetConsole.Node.Evidence/Services/EvidenceService.cs b/src/node/LibplanetConsole.Node.Evidence/Services/EvidenceService.cs deleted file mode 100644 index b0dd6ebf..00000000 --- a/src/node/LibplanetConsole.Node.Evidence/Services/EvidenceService.cs +++ /dev/null @@ -1,29 +0,0 @@ -// using LibplanetConsole.Evidence; -// using LibplanetConsole.Evidence.Services; - -// namespace LibplanetConsole.Node.Evidence.Services; - -// internal sealed class EvidenceService(Evidence evidence) -// : LocalService, IEvidenceService -// { -// public Task AddEvidenceAsync(CancellationToken cancellationToken) -// => evidence.AddEvidenceAsync(cancellationToken); - -// public Task GetEvidenceAsync(long height, CancellationToken cancellationToken) -// => evidence.GetEvidenceAsync(height, cancellationToken); - -// public Task ViolateAsync(CancellationToken cancellationToken) -// => evidence.ViolateAsync(cancellationToken); - -// #if LIBPLANET_DPOS -// public async Task UnjailAsync(byte[] signarue, CancellationToken cancellationToken) -// { -// if (node.Verify(true, signarue) == true) -// { -// await evidence.UnjailAsync(cancellationToken); -// } - -// throw new ArgumentException("Invalid signature.", nameof(signarue)); -// } -// #endif // LIBPLANET_DPOS -// } diff --git a/src/node/LibplanetConsole.Node.Evidence/Services/EvidenceServiceGrpcV1.cs b/src/node/LibplanetConsole.Node.Evidence/Services/EvidenceServiceGrpcV1.cs new file mode 100644 index 00000000..f0a368e7 --- /dev/null +++ b/src/node/LibplanetConsole.Node.Evidence/Services/EvidenceServiceGrpcV1.cs @@ -0,0 +1,39 @@ +using Grpc.Core; +using LibplanetConsole.Evidence; +using LibplanetConsole.Evidence.Grpc; + +namespace LibplanetConsole.Node.Evidence.Services; + +internal sealed class EvidenceServiceGrpcV1(Evidence evidence) + : EvidenceGrpcService.EvidenceGrpcServiceBase +{ + public override async Task AddEvidence( + AddEvidenceRequest request, ServerCallContext context) + { + var evidenceInfo = await evidence.AddEvidenceAsync(context.CancellationToken); + return new AddEvidenceResponse + { + EvidenceInfo = evidenceInfo, + }; + } + + public override async Task GetEvidence( + GetEvidenceRequest request, ServerCallContext context) + { + var height = request.Height; + var evidenceInfos = await evidence.GetEvidenceAsync(height, context.CancellationToken); + var response = new GetEvidenceResponse(); + for (var i = 0; i < evidenceInfos.Length; i++) + { + response.EvidenceInfos.Add(evidenceInfos[i]); + } + + return response; + } + + public override Task Violate( + ViolateRequest request, ServerCallContext context) + { + return base.Violate(request, context); + } +} diff --git a/src/node/LibplanetConsole.Node.Executable/EntryCommands/StartCommand.cs b/src/node/LibplanetConsole.Node.Executable/EntryCommands/StartCommand.cs index 53e8a497..96de60e7 100644 --- a/src/node/LibplanetConsole.Node.Executable/EntryCommands/StartCommand.cs +++ b/src/node/LibplanetConsole.Node.Executable/EntryCommands/StartCommand.cs @@ -77,12 +77,8 @@ protected override async Task OnExecuteAsync(CancellationToken cancellationToken app.UseAuthorization(); var @out = Console.Out; + await @out.WriteLineAsync(); await app.RunAsync(cancellationToken); - // var application = app.Services.GetRequiredService(); - // await @out.WriteLineAsync(); - // await application.RunAsync(); - await @out.WriteLineAsync("\u001b0"); - // await app.StopAsync(cancellationToken); } catch (CommandParsingException e) { diff --git a/src/node/LibplanetConsole.Node.Executable/ServiceCollectionExtensions.cs b/src/node/LibplanetConsole.Node.Executable/ServiceCollectionExtensions.cs index 2a454b1b..3ebe888d 100644 --- a/src/node/LibplanetConsole.Node.Executable/ServiceCollectionExtensions.cs +++ b/src/node/LibplanetConsole.Node.Executable/ServiceCollectionExtensions.cs @@ -15,9 +15,6 @@ public static IServiceCollection AddApplication( { @this.AddLogging(options.LogPath, options.LibraryLogPath); - // @this.AddSingleton(s => new Application(s, options)); - // @this.AddSingleton(s => s.GetRequiredService()); - @this.AddSingleton(); @this.AddSingleton(); @this.AddHostedService(); diff --git a/src/node/LibplanetConsole.Node.Executable/SystemTerminal.cs b/src/node/LibplanetConsole.Node.Executable/SystemTerminal.cs index f1452779..de00a093 100644 --- a/src/node/LibplanetConsole.Node.Executable/SystemTerminal.cs +++ b/src/node/LibplanetConsole.Node.Executable/SystemTerminal.cs @@ -13,6 +13,7 @@ public SystemTerminal( _commandContext = commandContext; _commandContext.Owner = applicationLifetime; Prompt = "libplanet-node $ "; + applicationLifetime.ApplicationStopping.Register(() => Prompt = "\u001b0"); } protected override string FormatPrompt(string prompt) => prompt; diff --git a/src/node/LibplanetConsole.Node.Executable/TerminalHostedService.cs b/src/node/LibplanetConsole.Node.Executable/TerminalHostedService.cs index 73264166..d26139f2 100644 --- a/src/node/LibplanetConsole.Node.Executable/TerminalHostedService.cs +++ b/src/node/LibplanetConsole.Node.Executable/TerminalHostedService.cs @@ -1,7 +1,6 @@ using JSSoft.Commands.Extensions; using JSSoft.Terminals; using LibplanetConsole.Common.Extensions; -using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Node.Executable; @@ -9,73 +8,73 @@ internal sealed class TerminalHostedService( IHostApplicationLifetime applicationLifetime, CommandContext commandContext, SystemTerminal terminal, - ApplicationOptions _options) : IHostedService + ApplicationOptions options) : IHostedService, IDisposable { + private readonly CancellationTokenSource _cancellationTokenSource = new(); private Task _runningTask = Task.CompletedTask; + private Task _waitInputTask = Task.CompletedTask; public async Task StartAsync(CancellationToken cancellationToken) { - if (_options.NoREPL != true) + if (options.NoREPL != true) { applicationLifetime.ApplicationStarted.Register(async () => { var sw = new StringWriter(); commandContext.Out = sw; - // await base.OnRunAsync(cancellationToken); await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); await commandContext.ExecuteAsync(["--help"], cancellationToken: default); await sw.WriteLineAsync(); await commandContext.ExecuteAsync(args: [], cancellationToken: default); await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); commandContext.Out = Console.Out; - // await sw.WriteLineIfAsync(GetStartupCondition(_options), GetStartupMessage()); + await sw.WriteLineIfAsync(GetStartupCondition(options), GetStartupMessage()); await Console.Out.WriteAsync(sw.ToString()); - _runningTask = terminal.StartAsync(cancellationToken); + _runningTask = terminal.StartAsync(_cancellationTokenSource.Token); + }); + applicationLifetime.ApplicationStopping.Register(() => + { + _cancellationTokenSource.Cancel(); }); } - else if (_options.ParentProcessId == 0) - { - // _waitInputTask = WaitInputAsync(); - } - else + else if (options.ParentProcessId == 0) { - // await base.OnRunAsync(cancellationToken); + _waitInputTask = WaitInputAsync(); } + + await Task.CompletedTask; } public async Task StopAsync(CancellationToken cancellationToken) { await _runningTask; + await _waitInputTask; await terminal.StopAsync(cancellationToken); } - // protected override async ValueTask OnDisposeAsync() - // { - // await base.OnDisposeAsync(); - // await _waitInputTask; - // } + void IDisposable.Dispose() => _cancellationTokenSource.Dispose(); - // private static bool GetStartupCondition(ApplicationOptions options) - // { - // if (options.SeedEndPoint is not null) - // { - // return false; - // } + private static bool GetStartupCondition(ApplicationOptions options) + { + if (options.SeedEndPoint is not null) + { + return false; + } - // return options.ParentProcessId == 0; - // } + return options.ParentProcessId == 0; + } - // private static string GetStartupMessage() - // { - // var startText = TerminalStringBuilder.GetString("start", TerminalColorType.Green); - // return $"\nType '{startText}' to start the node."; - // } + private static string GetStartupMessage() + { + var startText = TerminalStringBuilder.GetString("start", TerminalColorType.Green); + return $"\nType '{startText}' to start the node."; + } - // private async Task WaitInputAsync() - // { - // await Console.Out.WriteLineAsync("Press any key to exit."); - // await Task.Run(() => Console.ReadKey(intercept: true)); - // Cancel(); - // } + private async Task WaitInputAsync() + { + await Console.Out.WriteLineAsync("Press any key to exit."); + await Task.Run(() => Console.ReadKey(intercept: true)); + applicationLifetime.StopApplication(); + } } diff --git a/src/node/LibplanetConsole.Node/ApplicationInfoProvider.cs b/src/node/LibplanetConsole.Node/ApplicationInfoProvider.cs index 7290644e..aaf235ec 100644 --- a/src/node/LibplanetConsole.Node/ApplicationInfoProvider.cs +++ b/src/node/LibplanetConsole.Node/ApplicationInfoProvider.cs @@ -1,13 +1,25 @@ -// using LibplanetConsole.Common; +using LibplanetConsole.Common; +using Microsoft.Extensions.Hosting; -// namespace LibplanetConsole.Node; +namespace LibplanetConsole.Node; -// internal sealed class ApplicationInfoProvider : InfoProviderBase -// { -// public ApplicationInfoProvider() -// : base("Application") -// { -// } +internal sealed class ApplicationInfoProvider + : InfoProviderBase +{ + private readonly ApplicationInfo _info; -// protected override object? GetInfo(ApplicationBase obj) => obj.Info; -// } + public ApplicationInfoProvider(ApplicationOptions options) + : base("Application") + { + _info = new() + { + EndPoint = options.EndPoint, + SeedEndPoint = options.SeedEndPoint, + StorePath = options.StorePath, + LogPath = options.LogPath, + ParentProcessId = options.ParentProcessId, + }; + } + + protected override object? GetInfo(IHostApplicationLifetime obj) => _info; +} diff --git a/src/node/LibplanetConsole.Node/Commands/ExitCommand.cs b/src/node/LibplanetConsole.Node/Commands/ExitCommand.cs index ef31c253..8e963592 100644 --- a/src/node/LibplanetConsole.Node/Commands/ExitCommand.cs +++ b/src/node/LibplanetConsole.Node/Commands/ExitCommand.cs @@ -10,11 +10,14 @@ internal sealed class ExitCommand(IHostApplicationLifetime applicationLifetime) protected override async Task OnExecuteAsync(CancellationToken cancellationToken) { var resetEvent = new ManualResetEvent(false); - applicationLifetime.ApplicationStopped.Register(() => resetEvent.Set()); + applicationLifetime.ApplicationStopping.Register(() => + { + resetEvent.Set(); + }); applicationLifetime.StopApplication(); - while (!resetEvent.WaitOne(100)) + while (!resetEvent.WaitOne(1)) { - await Task.Yield(); + await Task.Delay(1, default); } } } diff --git a/src/node/LibplanetConsole.Node/Commands/InfoCommand.cs b/src/node/LibplanetConsole.Node/Commands/InfoCommand.cs index 4f1e07c1..7fe22163 100644 --- a/src/node/LibplanetConsole.Node/Commands/InfoCommand.cs +++ b/src/node/LibplanetConsole.Node/Commands/InfoCommand.cs @@ -1,15 +1,18 @@ -// using JSSoft.Commands; -// using LibplanetConsole.Common; -// using LibplanetConsole.Common.Extensions; +using JSSoft.Commands; +using LibplanetConsole.Common; +using LibplanetConsole.Common.Extensions; +using Microsoft.Extensions.Hosting; -// namespace LibplanetConsole.Node.Commands; +namespace LibplanetConsole.Node.Commands; -// [CommandSummary("Print node application information.")] -// internal sealed class InfoCommand(IApplication application) : CommandBase -// { -// protected override void OnExecute() -// { -// var info = InfoUtility.GetInfo(serviceProvider: application, obj: application); -// Out.WriteLineAsJson(info); -// } -// } +[CommandSummary("Print node application information.")] +internal sealed class InfoCommand( + IServiceProvider serviceProvider, IHostApplicationLifetime applicationLifetime) + : CommandBase +{ + protected override void OnExecute() + { + var info = InfoUtility.GetInfo(serviceProvider: serviceProvider, obj: applicationLifetime); + Out.WriteLineAsJson(info); + } +} diff --git a/src/node/LibplanetConsole.Node/IApplication.cs b/src/node/LibplanetConsole.Node/IApplication.cs deleted file mode 100644 index 9b82ffdb..00000000 --- a/src/node/LibplanetConsole.Node/IApplication.cs +++ /dev/null @@ -1,12 +0,0 @@ -// namespace LibplanetConsole.Node; - -// public interface IApplication : IAsyncDisposable, IServiceProvider -// { -// ApplicationInfo Info { get; } - -// Task InvokeAsync(Action action, CancellationToken cancellationToken); - -// Task InvokeAsync(Func func, CancellationToken cancellationToken); - -// void Cancel(); -// } diff --git a/src/node/LibplanetConsole.Node/IGenesisCreator.cs b/src/node/LibplanetConsole.Node/IGenesisCreator.cs deleted file mode 100644 index 5264061c..00000000 --- a/src/node/LibplanetConsole.Node/IGenesisCreator.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace LibplanetConsole.Node; - -public interface IGenesisCreator -{ - byte[] Create(); -} diff --git a/src/node/LibplanetConsole.Node/NodeInfoProvider.cs b/src/node/LibplanetConsole.Node/NodeInfoProvider.cs index 36df5415..47c938f3 100644 --- a/src/node/LibplanetConsole.Node/NodeInfoProvider.cs +++ b/src/node/LibplanetConsole.Node/NodeInfoProvider.cs @@ -1,8 +1,10 @@ -// using LibplanetConsole.Common; +using LibplanetConsole.Common; +using Microsoft.Extensions.Hosting; -// namespace LibplanetConsole.Node; +namespace LibplanetConsole.Node; -// internal sealed class NodeInfoProvider(Node node) : InfoProviderBase(nameof(Node)) -// { -// protected override object? GetInfo(ApplicationBase obj) => node.Info; -// } +internal sealed class NodeInfoProvider(Node node) + : InfoProviderBase(nameof(Node)) +{ + protected override object? GetInfo(IHostApplicationLifetime obj) => node.Info; +} diff --git a/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs b/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs index 58e45ecd..e724470e 100644 --- a/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs +++ b/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ using JSSoft.Commands; +using LibplanetConsole.Common; using LibplanetConsole.Node.Commands; using LibplanetConsole.Seed; using Microsoft.Extensions.DependencyInjection; @@ -10,8 +11,9 @@ public static class ServiceCollectionExtensions public static IServiceCollection AddNode( this IServiceCollection @this, ApplicationOptions options) { - SynchronizationContext.SetSynchronizationContext(new()); - @this.AddSingleton(SynchronizationContext.Current); + var synchronizationContext = SynchronizationContext.Current ?? new(); + SynchronizationContext.SetSynchronizationContext(synchronizationContext); + @this.AddSingleton(synchronizationContext); @this.AddSingleton(options); @this.AddSingleton() .AddSingleton(s => s.GetRequiredService()) @@ -20,13 +22,12 @@ public static IServiceCollection AddNode( .AddSingleton(s => s.GetRequiredService()) .AddSingleton(s => s.GetRequiredService()); @this.AddHostedService(); - // @this.AddSingleton(); - // @this.AddSingleton(); - + @this.AddSingleton(); + @this.AddSingleton(); @this.AddSingleton(); @this.AddSingleton(); - // @this.AddSingleton(); + @this.AddSingleton(); @this.AddSingleton(); @this.AddSingleton(); @this.AddSingleton(); diff --git a/src/shared/LibplanetConsole.Evidence/EvidenceInfo.cs b/src/shared/LibplanetConsole.Evidence/EvidenceInfo.cs index 2d9a7baf..fbbfbf06 100644 --- a/src/shared/LibplanetConsole.Evidence/EvidenceInfo.cs +++ b/src/shared/LibplanetConsole.Evidence/EvidenceInfo.cs @@ -1,4 +1,5 @@ using Libplanet.Types.Evidence; +using GrpcEvidenceInfo = LibplanetConsole.Evidence.Grpc.EvidenceInfo; namespace LibplanetConsole.Evidence; @@ -25,4 +26,29 @@ public static explicit operator EvidenceInfo(EvidenceBase evidence) Timestamp = evidence.Timestamp, }; } + + public static implicit operator EvidenceInfo(GrpcEvidenceInfo evidenceInfo) + { + return new EvidenceInfo + { + Type = evidenceInfo.Type, + Id = evidenceInfo.Id, + TargetAddress = new Address(evidenceInfo.TargetAddress), + Height = evidenceInfo.Height, + Timestamp = evidenceInfo.Timestamp.ToDateTimeOffset(), + }; + } + + public static implicit operator GrpcEvidenceInfo(EvidenceInfo evidenceInfo) + { + return new GrpcEvidenceInfo + { + Type = evidenceInfo.Type, + Id = evidenceInfo.Id, + TargetAddress = evidenceInfo.TargetAddress.ToHex(), + Height = evidenceInfo.Height, + Timestamp = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTimeOffset( + evidenceInfo.Timestamp), + }; + } } diff --git a/src/shared/LibplanetConsole.Evidence/Protos/EvidenceGrpcService.proto b/src/shared/LibplanetConsole.Evidence/Protos/EvidenceGrpcService.proto new file mode 100644 index 00000000..bd62d5b1 --- /dev/null +++ b/src/shared/LibplanetConsole.Evidence/Protos/EvidenceGrpcService.proto @@ -0,0 +1,45 @@ +syntax = "proto3"; + +option csharp_namespace = "LibplanetConsole.Evidence.Grpc"; + +package libplanet.console.evidence.v1; + +import "google/protobuf/timestamp.proto"; + +service EvidenceGrpcService { + rpc AddEvidence(AddEvidenceRequest) returns (AddEvidenceResponse); + rpc GetEvidence(GetEvidenceRequest) returns (GetEvidenceResponse); + rpc Violate(ViolateRequest) returns (ViolateResponse); +} + +message EvidenceInfo { + string type = 1; + string id = 2; + string targetAddress = 3; + int64 height = 4; + google.protobuf.Timestamp timestamp = 5; + string genesisHash = 6; + string tipHash = 7; + bool isRunning = 8; +} + +message AddEvidenceRequest { +} + +message AddEvidenceResponse { + EvidenceInfo evidenceInfo = 1; +} + +message GetEvidenceRequest { + int64 height = 1; +} + +message GetEvidenceResponse { + repeated EvidenceInfo evidenceInfos = 1; +} + +message ViolateRequest { +} + +message ViolateResponse { +} diff --git a/src/shared/LibplanetConsole.Evidence/Services/IEvidenceService.cs b/src/shared/LibplanetConsole.Evidence/Services/IEvidenceService.cs deleted file mode 100644 index 222423d7..00000000 --- a/src/shared/LibplanetConsole.Evidence/Services/IEvidenceService.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace LibplanetConsole.Evidence.Services; - -public interface IEvidenceService -{ - Task AddEvidenceAsync(CancellationToken cancellationToken); - - Task GetEvidenceAsync(long height, CancellationToken cancellationToken); - - Task ViolateAsync(CancellationToken cancellationToken); - -#if LIBPLANET_DPOS - Task UnjailAsync(byte[] signarue, CancellationToken cancellationToken); -#endif // LIBPLANET_DPOS -} diff --git a/src/shared/LibplanetConsole.Node/Services/IBlockChainService.cs b/src/shared/LibplanetConsole.Node/Services/IBlockChainService.cs index 3012d2ed..edef27c6 100644 --- a/src/shared/LibplanetConsole.Node/Services/IBlockChainService.cs +++ b/src/shared/LibplanetConsole.Node/Services/IBlockChainService.cs @@ -1,28 +1,28 @@ -using System.Security.Cryptography; +// using System.Security.Cryptography; -namespace LibplanetConsole.Node.Services; +// namespace LibplanetConsole.Node.Services; -public interface IBlockChainService -{ - Task SendTransactionAsync(byte[] transaction, CancellationToken cancellationToken); +// public interface IBlockChainService +// { +// Task SendTransactionAsync(byte[] transaction, CancellationToken cancellationToken); - Task GetNextNonceAsync(Address address, CancellationToken cancellationToken); +// Task GetNextNonceAsync(Address address, CancellationToken cancellationToken); - Task GetTipHashAsync(CancellationToken cancellationToken); +// Task GetTipHashAsync(CancellationToken cancellationToken); - Task GetStateAsync( - BlockHash? blockHash, - Address accountAddress, - Address address, - CancellationToken cancellationToken); +// Task GetStateAsync( +// BlockHash? blockHash, +// Address accountAddress, +// Address address, +// CancellationToken cancellationToken); - Task GetStateByStateRootHashAsync( - HashDigest stateRootHash, - Address accountAddress, - Address address, - CancellationToken cancellationToken); +// Task GetStateByStateRootHashAsync( +// HashDigest stateRootHash, +// Address accountAddress, +// Address address, +// CancellationToken cancellationToken); - Task GetBlockHashAsync(long height, CancellationToken cancellationToken); +// Task GetBlockHashAsync(long height, CancellationToken cancellationToken); - Task GetActionAsync(TxId txId, int actionIndex, CancellationToken cancellationToken); -} +// Task GetActionAsync(TxId txId, int actionIndex, CancellationToken cancellationToken); +// } diff --git a/src/shared/LibplanetConsole.Node/Services/INodeService.cs b/src/shared/LibplanetConsole.Node/Services/INodeService.cs index c8a45127..d03687b4 100644 --- a/src/shared/LibplanetConsole.Node/Services/INodeService.cs +++ b/src/shared/LibplanetConsole.Node/Services/INodeService.cs @@ -1,10 +1,10 @@ -namespace LibplanetConsole.Node.Services; +// namespace LibplanetConsole.Node.Services; -public interface INodeService -{ - Task StartAsync(string seedEndPoint, CancellationToken cancellationToken); +// public interface INodeService +// { +// Task StartAsync(string seedEndPoint, CancellationToken cancellationToken); - Task StopAsync(CancellationToken cancellationToken); +// Task StopAsync(CancellationToken cancellationToken); - Task GetInfoAsync(CancellationToken cancellationToken); -} +// Task GetInfoAsync(CancellationToken cancellationToken); +// } From 460d130306d952be37c352b738498ead24f11bf8 Mon Sep 17 00:00:00 2001 From: s2quake Date: Sun, 6 Oct 2024 20:31:12 +0900 Subject: [PATCH 06/18] refactor: Block appended event in client --- .gitignore | 1 + .vscode/launch.json | 26 ++- .../Application.cs | 114 ++++++------ .../EntryCommands/RunCommand.cs | 20 +-- .../EntryCommands/StartCommand.cs | 35 +++- .../LibplanetConsole.Client.Executable.csproj | 2 +- .../ServiceCollectionExtensions.cs | 10 +- .../SystemTerminal.cs | 6 +- .../TerminalHostedService.cs | 81 +++++++++ .../Tracers/BlockChainEventTracer.cs | 27 ++- .../Tracers/ClientEventTracer.cs | 36 ++-- .../ApplicationBase.cs | 164 +++++++++--------- .../ApplicationInfoProvider.cs | 18 +- src/client/LibplanetConsole.Client/Client.cs | 58 ++++++- .../ClientHostedService.cs | 36 ++++ .../ClientInfoProvider.cs | 5 +- .../Commands/ExitCommand.cs | 18 +- .../Commands/InfoCommand.cs | 6 +- .../LibplanetConsole.Client/IApplication.cs | 16 +- .../ServiceCollectionExtensions.cs | 7 +- .../Services/ClientService.cs | 49 ------ .../Services/ClientServiceContext.cs | 8 - .../Services/RemoteBlockChainService.cs | 9 - .../Services/RemoteNodeContext.cs | 9 - .../Services/RemoteNodeService.cs | 9 - .../Node.BlockChain.cs | 11 +- src/console/LibplanetConsole.Console/Node.cs | 39 ++--- .../Services/BlockChainGrpcServiceV1.cs | 49 +++++- .../Services/Callback.cs | 47 +++++ .../LibplanetConsole.Node/BlockInfo.Node.cs | 1 - src/shared/LibplanetConsole.Node/BlockInfo.cs | 22 ++- .../Protos/BlockChainGrpcService.proto | 119 ++++++++----- .../Services/IBlockChainCallback.cs | 6 - .../Services/IBlockChainService.cs | 28 --- .../Services/INodeCallback.cs | 8 - .../Services/INodeService.cs | 10 -- 36 files changed, 681 insertions(+), 429 deletions(-) create mode 100644 src/client/LibplanetConsole.Client.Executable/TerminalHostedService.cs create mode 100644 src/client/LibplanetConsole.Client/ClientHostedService.cs delete mode 100644 src/client/LibplanetConsole.Client/Services/ClientService.cs delete mode 100644 src/client/LibplanetConsole.Client/Services/ClientServiceContext.cs delete mode 100644 src/client/LibplanetConsole.Client/Services/RemoteBlockChainService.cs delete mode 100644 src/client/LibplanetConsole.Client/Services/RemoteNodeContext.cs delete mode 100644 src/client/LibplanetConsole.Client/Services/RemoteNodeService.cs create mode 100644 src/node/LibplanetConsole.Node/Services/Callback.cs delete mode 100644 src/shared/LibplanetConsole.Node/Services/IBlockChainCallback.cs delete mode 100644 src/shared/LibplanetConsole.Node/Services/IBlockChainService.cs delete mode 100644 src/shared/LibplanetConsole.Node/Services/INodeCallback.cs delete mode 100644 src/shared/LibplanetConsole.Node/Services/INodeService.cs diff --git a/.gitignore b/.gitignore index b410e99d..c7f1fcad 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ bin/ obj/ .data .node +.client .store .log .DS_Store diff --git a/.vscode/launch.json b/.vscode/launch.json index 6cf83172..e4052eec 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -69,6 +69,28 @@ "run" ] }, + { + "name": "C#: Libplanet Client - Initialize", + "type": "coreclr", + "request": "launch", + "program": "${workspaceFolder}/src/client/LibplanetConsole.Client.Executable/bin/Debug/net8.0/libplanet-client.dll", + "console": "integratedTerminal", + "args": [ + "init", + ".client" + ] + }, + { + "name": "C#: Libplanet Client - Start", + "type": "coreclr", + "request": "launch", + "program": "${workspaceFolder}/src/client/LibplanetConsole.Client.Executable/bin/Debug/net8.0/libplanet-client.dll", + "console": "integratedTerminal", + "args": [ + "start", + ".client" + ] + }, { "name": "C#: Libplanet Client", "type": "coreclr", @@ -114,8 +136,8 @@ { "name": "Node and Client", "configurations": [ - "C#: Libplanet Node - Run", - "C#: Libplanet Client" + "C#: Libplanet Node - Start", + "C#: Libplanet Client - Start" ] } ] diff --git a/src/client/LibplanetConsole.Client.Executable/Application.cs b/src/client/LibplanetConsole.Client.Executable/Application.cs index 5f636f11..a0a20484 100644 --- a/src/client/LibplanetConsole.Client.Executable/Application.cs +++ b/src/client/LibplanetConsole.Client.Executable/Application.cs @@ -1,64 +1,64 @@ -using JSSoft.Commands.Extensions; -using JSSoft.Terminals; -using LibplanetConsole.Common.Extensions; -using Microsoft.Extensions.DependencyInjection; +// using JSSoft.Commands.Extensions; +// using JSSoft.Terminals; +// using LibplanetConsole.Common.Extensions; +// using Microsoft.Extensions.DependencyInjection; -namespace LibplanetConsole.Client.Executable; +// namespace LibplanetConsole.Client.Executable; -internal sealed class Application(IServiceProvider serviceProvider, ApplicationOptions options) - : ApplicationBase(serviceProvider, options) -{ - private readonly ApplicationOptions _options = options; - private Task _waitInputTask = Task.CompletedTask; +// internal sealed class Application(IServiceProvider serviceProvider, ApplicationOptions options) +// : ApplicationBase(serviceProvider, options) +// { +// private readonly ApplicationOptions _options = options; +// private Task _waitInputTask = Task.CompletedTask; - protected override async Task OnRunAsync(CancellationToken cancellationToken) - { - if (_options.NoREPL != true) - { - var sw = new StringWriter(); - var startupCondition = _options.NodeEndPoint is null && _options.ParentProcessId == 0; - var commandContext = this.GetRequiredService(); - var terminal = this.GetRequiredService(); - commandContext.Out = sw; - await base.OnRunAsync(cancellationToken); - await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); - await commandContext.ExecuteAsync(["--help"], cancellationToken: default); - await sw.WriteLineAsync(); - await commandContext.ExecuteAsync(args: [], cancellationToken: default); - await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); - commandContext.Out = Console.Out; - await sw.WriteLineIfAsync(startupCondition, GetStartupMessage()); - await Console.Out.WriteAsync(sw.ToString()); +// protected override async Task OnRunAsync(CancellationToken cancellationToken) +// { +// if (_options.NoREPL != true) +// { +// var sw = new StringWriter(); +// var startupCondition = _options.NodeEndPoint is null && _options.ParentProcessId == 0; +// var commandContext = this.GetRequiredService(); +// var terminal = this.GetRequiredService(); +// commandContext.Out = sw; +// await base.OnRunAsync(cancellationToken); +// await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); +// await commandContext.ExecuteAsync(["--help"], cancellationToken: default); +// await sw.WriteLineAsync(); +// await commandContext.ExecuteAsync(args: [], cancellationToken: default); +// await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); +// commandContext.Out = Console.Out; +// await sw.WriteLineIfAsync(startupCondition, GetStartupMessage()); +// await Console.Out.WriteAsync(sw.ToString()); - await terminal.StartAsync(cancellationToken); - } - else if (_options.ParentProcessId == 0) - { - await base.OnRunAsync(cancellationToken); - _waitInputTask = WaitInputAsync(); - } - else - { - await base.OnRunAsync(cancellationToken); - } - } +// await terminal.StartAsync(cancellationToken); +// } +// else if (_options.ParentProcessId == 0) +// { +// await base.OnRunAsync(cancellationToken); +// _waitInputTask = WaitInputAsync(); +// } +// else +// { +// await base.OnRunAsync(cancellationToken); +// } +// } - protected override async ValueTask OnDisposeAsync() - { - await base.OnDisposeAsync(); - await _waitInputTask; - } +// protected override async ValueTask OnDisposeAsync() +// { +// await base.OnDisposeAsync(); +// await _waitInputTask; +// } - private static string GetStartupMessage() - { - var startText = TerminalStringBuilder.GetString("start", TerminalColorType.Green); - return $"\nType '{startText} ' to connect to the node."; - } +// private static string GetStartupMessage() +// { +// var startText = TerminalStringBuilder.GetString("start", TerminalColorType.Green); +// return $"\nType '{startText} ' to connect to the node."; +// } - private async Task WaitInputAsync() - { - await Console.Out.WriteLineAsync("Press any key to exit."); - await Task.Run(() => Console.ReadKey(intercept: true)); - Cancel(); - } -} +// private async Task WaitInputAsync() +// { +// await Console.Out.WriteLineAsync("Press any key to exit."); +// await Task.Run(() => Console.ReadKey(intercept: true)); +// Cancel(); +// } +// } diff --git a/src/client/LibplanetConsole.Client.Executable/EntryCommands/RunCommand.cs b/src/client/LibplanetConsole.Client.Executable/EntryCommands/RunCommand.cs index 9a1ad870..b68b4199 100644 --- a/src/client/LibplanetConsole.Client.Executable/EntryCommands/RunCommand.cs +++ b/src/client/LibplanetConsole.Client.Executable/EntryCommands/RunCommand.cs @@ -28,18 +28,18 @@ protected override async Task OnExecuteAsync(CancellationToken cancellationToken { try { - var serviceCollection = new ApplicationServiceCollection(_settingsCollection); - var applicationOptions = _applicationSettings.ToOptions(); + // var serviceCollection = new ApplicationServiceCollection(_settingsCollection); + // var applicationOptions = _applicationSettings.ToOptions(); - serviceCollection.AddClient(applicationOptions); - serviceCollection.AddApplication(applicationOptions); + // serviceCollection.AddClient(applicationOptions); + // serviceCollection.AddApplication(applicationOptions); - await using var serviceProvider = serviceCollection.BuildServiceProvider(); - var @out = Console.Out; - var application = serviceProvider.GetRequiredService(); - await @out.WriteLineAsync(); - await application.RunAsync(); - await @out.WriteLineAsync("\u001b0"); + // await using var serviceProvider = serviceCollection.BuildServiceProvider(); + // var @out = Console.Out; + // var application = serviceProvider.GetRequiredService(); + // await @out.WriteLineAsync(); + // await application.RunAsync(); + // await @out.WriteLineAsync("\u001b0"); } catch (CommandParsingException e) { diff --git a/src/client/LibplanetConsole.Client.Executable/EntryCommands/StartCommand.cs b/src/client/LibplanetConsole.Client.Executable/EntryCommands/StartCommand.cs index 40700b88..38575e3d 100644 --- a/src/client/LibplanetConsole.Client.Executable/EntryCommands/StartCommand.cs +++ b/src/client/LibplanetConsole.Client.Executable/EntryCommands/StartCommand.cs @@ -1,8 +1,10 @@ using System.ComponentModel; using JSSoft.Commands; +using LibplanetConsole.Common; using LibplanetConsole.DataAnnotations; using LibplanetConsole.Framework; using LibplanetConsole.Settings; +using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Client.Executable.EntryCommands; @@ -30,8 +32,9 @@ protected override async Task OnExecuteAsync(CancellationToken cancellationToken { try { + var builder = WebApplication.CreateBuilder(); + var settingsPath = Path.Combine(RepositoryPath, Repository.SettingsFileName); - var serviceCollection = new ApplicationServiceCollection(_settingsCollection); var applicationSettings = Load(settingsPath) with { ParentProcessId = ParentProcessId, @@ -39,15 +42,33 @@ protected override async Task OnExecuteAsync(CancellationToken cancellationToken }; var applicationOptions = applicationSettings.ToOptions(); - serviceCollection.AddClient(applicationOptions); - serviceCollection.AddApplication(applicationOptions); + foreach (var settings in _settingsCollection) + { + builder.Services.AddSingleton(settings.GetType(), settings); + } + + var (_, port) = EndPointUtility.GetHostAndPort(applicationOptions.EndPoint); + builder.WebHost.ConfigureKestrel(options => + { + // Setup a HTTP/2 endpoint without TLS. + options.ListenLocalhost(port, o => o.Protocols = HttpProtocols.Http2); + options.ListenLocalhost(port + 1, o => o.Protocols = HttpProtocols.Http1AndHttp2); + }); + + builder.Services.AddClient(applicationOptions); + builder.Services.AddApplication(applicationOptions); + + // builder.Services.AddGrpc(); + + using var app = builder.Build(); + + app.MapGet("/", () => "123"); + // app.UseAuthentication(); + // app.UseAuthorization(); - await using var serviceProvider = serviceCollection.BuildServiceProvider(); var @out = Console.Out; - var application = serviceProvider.GetRequiredService(); await @out.WriteLineAsync(); - await application.RunAsync(); - await @out.WriteLineAsync("\u001b0"); + await app.RunAsync(cancellationToken); } catch (CommandParsingException e) { diff --git a/src/client/LibplanetConsole.Client.Executable/LibplanetConsole.Client.Executable.csproj b/src/client/LibplanetConsole.Client.Executable/LibplanetConsole.Client.Executable.csproj index 4d6dbbbc..c69322c2 100644 --- a/src/client/LibplanetConsole.Client.Executable/LibplanetConsole.Client.Executable.csproj +++ b/src/client/LibplanetConsole.Client.Executable/LibplanetConsole.Client.Executable.csproj @@ -1,4 +1,4 @@ - + diff --git a/src/client/LibplanetConsole.Client.Executable/ServiceCollectionExtensions.cs b/src/client/LibplanetConsole.Client.Executable/ServiceCollectionExtensions.cs index 669dff97..b3d62236 100644 --- a/src/client/LibplanetConsole.Client.Executable/ServiceCollectionExtensions.cs +++ b/src/client/LibplanetConsole.Client.Executable/ServiceCollectionExtensions.cs @@ -12,21 +12,19 @@ internal static class ServiceCollectionExtensions public static IServiceCollection AddApplication( this IServiceCollection @this, ApplicationOptions options) { - @this.AddSingleton(s => new Application(s, options)); - @this.AddSingleton(s => s.GetRequiredService()); + @this.AddLogging(options.LogPath, string.Empty); @this.AddSingleton(); @this.AddSingleton(); + @this.AddHostedService(); @this.AddSingleton() .AddSingleton(s => s.GetRequiredService()); @this.AddSingleton() .AddSingleton(s => s.GetRequiredService()); - @this.AddSingleton(); - @this.AddSingleton(); - - @this.AddLogging(options.LogPath, string.Empty); + @this.AddHostedService(); + @this.AddHostedService(); return @this; } diff --git a/src/client/LibplanetConsole.Client.Executable/SystemTerminal.cs b/src/client/LibplanetConsole.Client.Executable/SystemTerminal.cs index 97665efa..190e7bbb 100644 --- a/src/client/LibplanetConsole.Client.Executable/SystemTerminal.cs +++ b/src/client/LibplanetConsole.Client.Executable/SystemTerminal.cs @@ -7,11 +7,13 @@ internal sealed class SystemTerminal : SystemTerminalBase { private readonly CommandContext _commandContext; - public SystemTerminal(IApplication application, CommandContext commandContext) + public SystemTerminal( + IHostApplicationLifetime applicationLifetime, CommandContext commandContext) { _commandContext = commandContext; - _commandContext.Owner = application; + _commandContext.Owner = applicationLifetime; Prompt = "libplanet-client $ "; + applicationLifetime.ApplicationStopping.Register(() => Prompt = "\u001b0"); } protected override string FormatPrompt(string prompt) => prompt; diff --git a/src/client/LibplanetConsole.Client.Executable/TerminalHostedService.cs b/src/client/LibplanetConsole.Client.Executable/TerminalHostedService.cs new file mode 100644 index 00000000..9fde140f --- /dev/null +++ b/src/client/LibplanetConsole.Client.Executable/TerminalHostedService.cs @@ -0,0 +1,81 @@ +using JSSoft.Commands.Extensions; +using JSSoft.Terminals; +using LibplanetConsole.Common.Extensions; +using Microsoft.Extensions.Hosting; + +namespace LibplanetConsole.Client.Executable; + +internal sealed class TerminalHostedService( + IHostApplicationLifetime applicationLifetime, + CommandContext commandContext, + SystemTerminal terminal, + ApplicationOptions options) : IHostedService, IDisposable +{ + private readonly CancellationTokenSource _cancellationTokenSource = new(); + private Task _runningTask = Task.CompletedTask; + private Task _waitInputTask = Task.CompletedTask; + + public async Task StartAsync(CancellationToken cancellationToken) + { + if (options.NoREPL != true) + { + applicationLifetime.ApplicationStarted.Register(async () => + { + var sw = new StringWriter(); + commandContext.Out = sw; + await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); + await commandContext.ExecuteAsync(["--help"], cancellationToken: default); + await sw.WriteLineAsync(); + await commandContext.ExecuteAsync(args: [], cancellationToken: default); + await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); + commandContext.Out = Console.Out; + await sw.WriteLineIfAsync(GetStartupCondition(options), GetStartupMessage()); + await Console.Out.WriteAsync(sw.ToString()); + + _runningTask = terminal.StartAsync(_cancellationTokenSource.Token); + }); + applicationLifetime.ApplicationStopping.Register(() => + { + _cancellationTokenSource.Cancel(); + }); + } + else if (options.ParentProcessId == 0) + { + _waitInputTask = WaitInputAsync(); + } + + await Task.CompletedTask; + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + await _runningTask; + await _waitInputTask; + await terminal.StopAsync(cancellationToken); + } + + void IDisposable.Dispose() => _cancellationTokenSource.Dispose(); + + private static bool GetStartupCondition(ApplicationOptions options) + { + if (options.NodeEndPoint is not null) + { + return false; + } + + return options.ParentProcessId == 0; + } + + private static string GetStartupMessage() + { + var startText = TerminalStringBuilder.GetString("start", TerminalColorType.Green); + return $"\nType '{startText}' to start the node."; + } + + private async Task WaitInputAsync() + { + await Console.Out.WriteLineAsync("Press any key to exit."); + await Task.Run(() => Console.ReadKey(intercept: true)); + applicationLifetime.StopApplication(); + } +} diff --git a/src/client/LibplanetConsole.Client.Executable/Tracers/BlockChainEventTracer.cs b/src/client/LibplanetConsole.Client.Executable/Tracers/BlockChainEventTracer.cs index d18077e9..21e3802e 100644 --- a/src/client/LibplanetConsole.Client.Executable/Tracers/BlockChainEventTracer.cs +++ b/src/client/LibplanetConsole.Client.Executable/Tracers/BlockChainEventTracer.cs @@ -1,24 +1,34 @@ using JSSoft.Terminals; +using LibplanetConsole.Client; using LibplanetConsole.Common.Extensions; -using LibplanetConsole.Framework; namespace LibplanetConsole.Client.Executable.Tracers; -internal sealed class BlockChainEventTracer(IBlockChain blockChain) - : IApplicationService, IDisposable +internal sealed class BlockChainEventTracer : IHostedService, IDisposable { - public Task InitializeAsync(CancellationToken cancellationToken) + private readonly IBlockChain _blockChain; + private readonly ILogger _logger; + + public BlockChainEventTracer( + IBlockChain blockChain, ILogger logger) { - blockChain.BlockAppended += BlockChain_BlockAppended; - return Task.CompletedTask; + _blockChain = blockChain; + _logger = logger; + _blockChain.BlockAppended += Client_BlockAppended; } + public Task StartAsync(CancellationToken cancellationToken) + => Task.CompletedTask; + + public Task StopAsync(CancellationToken cancellationToken) + => Task.CompletedTask; + void IDisposable.Dispose() { - blockChain.BlockAppended -= BlockChain_BlockAppended; + _blockChain.BlockAppended -= Client_BlockAppended; } - private void BlockChain_BlockAppended(object? sender, BlockEventArgs e) + private void Client_BlockAppended(object? sender, BlockEventArgs e) { var blockInfo = e.BlockInfo; var hash = blockInfo.Hash; @@ -26,5 +36,6 @@ private void BlockChain_BlockAppended(object? sender, BlockEventArgs e) var message = $"Block #{blockInfo.Height} '{hash.ToShortString()}' " + $"Appended by '{miner.ToShortString()}'"; Console.Out.WriteColoredLine(message, TerminalColorType.BrightGreen); + _logger.LogInformation(message); } } diff --git a/src/client/LibplanetConsole.Client.Executable/Tracers/ClientEventTracer.cs b/src/client/LibplanetConsole.Client.Executable/Tracers/ClientEventTracer.cs index 3e501c25..1a1be3e8 100644 --- a/src/client/LibplanetConsole.Client.Executable/Tracers/ClientEventTracer.cs +++ b/src/client/LibplanetConsole.Client.Executable/Tracers/ClientEventTracer.cs @@ -1,36 +1,44 @@ using JSSoft.Terminals; -using LibplanetConsole.Common; using LibplanetConsole.Common.Extensions; -using LibplanetConsole.Framework; namespace LibplanetConsole.Client.Executable.Tracers; -internal sealed class ClientEventTracer(IClient client) - : IApplicationService, IDisposable +internal sealed class ClientEventTracer : IHostedService, IDisposable { - public Task InitializeAsync(CancellationToken cancellationToken) + private readonly ApplicationOptions _options; + private readonly IClient _client; + + public ClientEventTracer(ApplicationOptions options, IClient client) { - client.Started += Client_Started; - client.Stopped += Client_Stopped; - return Task.CompletedTask; + _options = options; + _client = client; + _client.Started += Client_Started; + _client.Stopped += Client_Stopped; } + public Task StartAsync(CancellationToken cancellationToken) + => Task.CompletedTask; + + public Task StopAsync(CancellationToken cancellationToken) + => Task.CompletedTask; + void IDisposable.Dispose() { - client.Started -= Client_Started; - client.Stopped -= Client_Stopped; + _client.Started -= Client_Started; + _client.Stopped -= Client_Stopped; } private void Client_Started(object? sender, EventArgs e) { - var message = $"Client has been started."; + var endPoint = _options.EndPoint; + var message = $"BlockChain has been started.: {endPoint}"; Console.Out.WriteColoredLine(message, TerminalColorType.BrightGreen); - Console.Out.WriteLineAsJson(client.Info); } - private void Client_Stopped(object? sender, StopEventArgs e) + private void Client_Stopped(object? sender, EventArgs e) { - var message = $"Client has been stopped: {e.Reason}."; + var endPoint = _options.EndPoint; + var message = $"BlockChain has been stopped.: {endPoint}"; Console.Out.WriteColoredLine(message, TerminalColorType.BrightGreen); } } diff --git a/src/client/LibplanetConsole.Client/ApplicationBase.cs b/src/client/LibplanetConsole.Client/ApplicationBase.cs index edbf1ee8..6e42b3a5 100644 --- a/src/client/LibplanetConsole.Client/ApplicationBase.cs +++ b/src/client/LibplanetConsole.Client/ApplicationBase.cs @@ -1,95 +1,95 @@ -using System.Diagnostics; -using LibplanetConsole.Client.Services; -using LibplanetConsole.Common.Extensions; -using LibplanetConsole.Framework; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; +// using System.Diagnostics; +// using LibplanetConsole.Client.Services; +// using LibplanetConsole.Common.Extensions; +// using LibplanetConsole.Framework; +// using Microsoft.Extensions.DependencyInjection; +// using Microsoft.Extensions.Logging; -namespace LibplanetConsole.Client; +// namespace LibplanetConsole.Client; -public abstract class ApplicationBase : ApplicationFramework, IApplication -{ - private readonly IServiceProvider _serviceProvider; - private readonly Client _client; - private readonly Process? _parentProcess; - private readonly ApplicationInfo _info; - private readonly ILogger _logger; - private readonly Task _waitForExitTask = Task.CompletedTask; - // private ClientServiceContext? _clientServiceContext; - private Guid _closeToken; +// public abstract class ApplicationBase : ApplicationFramework, IApplication +// { +// private readonly IServiceProvider _serviceProvider; +// private readonly Client _client; +// private readonly Process? _parentProcess; +// private readonly ApplicationInfo _info; +// private readonly ILogger _logger; +// private readonly Task _waitForExitTask = Task.CompletedTask; +// // private ClientServiceContext? _clientServiceContext; +// private Guid _closeToken; - protected ApplicationBase(IServiceProvider serviceProvider, ApplicationOptions options) - : base(serviceProvider) - { - _serviceProvider = serviceProvider; - _logger = serviceProvider.GetLogger(); - _logger.LogDebug(Environment.CommandLine); - _logger.LogDebug("Application initializing..."); - _client = serviceProvider.GetRequiredService(); - _info = new() - { - EndPoint = options.EndPoint, - NodeEndPoint = options.NodeEndPoint, - LogPath = options.LogPath, - }; - if (options.ParentProcessId != 0 && - Process.GetProcessById(options.ParentProcessId) is { } parentProcess) - { - _parentProcess = parentProcess; - _waitForExitTask = WaitForExit(parentProcess, Cancel); - } +// protected ApplicationBase(IServiceProvider serviceProvider, ApplicationOptions options) +// : base(serviceProvider) +// { +// _serviceProvider = serviceProvider; +// _logger = serviceProvider.GetLogger(); +// _logger.LogDebug(Environment.CommandLine); +// _logger.LogDebug("Application initializing..."); +// _client = serviceProvider.GetRequiredService(); +// _info = new() +// { +// EndPoint = options.EndPoint, +// NodeEndPoint = options.NodeEndPoint, +// LogPath = options.LogPath, +// }; +// if (options.ParentProcessId != 0 && +// Process.GetProcessById(options.ParentProcessId) is { } parentProcess) +// { +// _parentProcess = parentProcess; +// _waitForExitTask = WaitForExit(parentProcess, Cancel); +// } - _logger.LogDebug("Application initialized."); - } +// _logger.LogDebug("Application initialized."); +// } - public EndPoint EndPoint => _info.EndPoint; +// public EndPoint EndPoint => _info.EndPoint; - public ApplicationInfo Info => _info; +// public ApplicationInfo Info => _info; - protected override bool CanClose => _parentProcess?.HasExited == true; +// protected override bool CanClose => _parentProcess?.HasExited == true; - public override object? GetService(Type serviceType) - => _serviceProvider.GetService(serviceType); +// public override object? GetService(Type serviceType) +// => _serviceProvider.GetService(serviceType); - protected override async Task OnRunAsync(CancellationToken cancellationToken) - { - _logger.LogDebug("ClientServiceContext is starting: {EndPoint}", _info.EndPoint); - // _clientServiceContext = _serviceProvider.GetRequiredService(); - // _clientServiceContext.EndPoint = _info.EndPoint; - // _closeToken = await _clientServiceContext.StartAsync(cancellationToken: default); - _logger.LogDebug("ClientServiceContext is started: {EndPoint}", _info.EndPoint); - await base.OnRunAsync(cancellationToken); - await AutoStartAsync(cancellationToken); - } +// protected override async Task OnRunAsync(CancellationToken cancellationToken) +// { +// _logger.LogDebug("ClientServiceContext is starting: {EndPoint}", _info.EndPoint); +// // _clientServiceContext = _serviceProvider.GetRequiredService(); +// // _clientServiceContext.EndPoint = _info.EndPoint; +// // _closeToken = await _clientServiceContext.StartAsync(cancellationToken: default); +// _logger.LogDebug("ClientServiceContext is started: {EndPoint}", _info.EndPoint); +// await base.OnRunAsync(cancellationToken); +// await AutoStartAsync(cancellationToken); +// } - protected override async ValueTask OnDisposeAsync() - { - await base.OnDisposeAsync(); - // if (_clientServiceContext is not null) - // { - // _logger.LogDebug("ClientServiceContext is closing: {EndPoint}", _info.EndPoint); - // await _clientServiceContext.CloseAsync(_closeToken, CancellationToken.None); - // _clientServiceContext = null; - // _logger.LogDebug("ClientServiceContext is closed: {EndPoint}", _info.EndPoint); - // } +// protected override async ValueTask OnDisposeAsync() +// { +// await base.OnDisposeAsync(); +// // if (_clientServiceContext is not null) +// // { +// // _logger.LogDebug("ClientServiceContext is closing: {EndPoint}", _info.EndPoint); +// // await _clientServiceContext.CloseAsync(_closeToken, CancellationToken.None); +// // _clientServiceContext = null; +// // _logger.LogDebug("ClientServiceContext is closed: {EndPoint}", _info.EndPoint); +// // } - await _waitForExitTask; - } +// await _waitForExitTask; +// } - private static async Task WaitForExit(Process process, Action cancelAction) - { - await process.WaitForExitAsync(); - cancelAction.Invoke(); - } +// private static async Task WaitForExit(Process process, Action cancelAction) +// { +// await process.WaitForExitAsync(); +// cancelAction.Invoke(); +// } - private async Task AutoStartAsync(CancellationToken cancellationToken) - { - if (_info.NodeEndPoint is { } nodeEndPoint) - { - _client.NodeEndPoint = nodeEndPoint; - _logger.LogDebug("Client auto-starting: {EndPoint}", nodeEndPoint); - await _client.StartAsync(cancellationToken); - _logger.LogDebug("Client auto-started: {EndPoint}", nodeEndPoint); - } - } -} +// private async Task AutoStartAsync(CancellationToken cancellationToken) +// { +// if (_info.NodeEndPoint is { } nodeEndPoint) +// { +// _client.NodeEndPoint = nodeEndPoint; +// _logger.LogDebug("Client auto-starting: {EndPoint}", nodeEndPoint); +// await _client.StartAsync(cancellationToken); +// _logger.LogDebug("Client auto-started: {EndPoint}", nodeEndPoint); +// } +// } +// } diff --git a/src/client/LibplanetConsole.Client/ApplicationInfoProvider.cs b/src/client/LibplanetConsole.Client/ApplicationInfoProvider.cs index beed1347..88b86d0b 100644 --- a/src/client/LibplanetConsole.Client/ApplicationInfoProvider.cs +++ b/src/client/LibplanetConsole.Client/ApplicationInfoProvider.cs @@ -1,13 +1,25 @@ using LibplanetConsole.Common; +using Microsoft.Extensions.Hosting; namespace LibplanetConsole.Client; -internal sealed class ApplicationInfoProvider : InfoProviderBase +internal sealed class ApplicationInfoProvider + : InfoProviderBase { - public ApplicationInfoProvider() + private readonly ApplicationInfo _info; + + public ApplicationInfoProvider(ApplicationOptions options) : base("Application") { + _info = new() + { + EndPoint = options.EndPoint, + // SeedEndPoint = options.SeedEndPoint, + // StorePath = options.StorePath, + LogPath = options.LogPath, + // ParentProcessId = options.ParentProcessId, + }; } - protected override object? GetInfo(ApplicationBase obj) => obj.Info; + protected override object? GetInfo(IHostApplicationLifetime obj) => _info; } diff --git a/src/client/LibplanetConsole.Client/Client.cs b/src/client/LibplanetConsole.Client/Client.cs index 42d31290..3327cf51 100644 --- a/src/client/LibplanetConsole.Client/Client.cs +++ b/src/client/LibplanetConsole.Client/Client.cs @@ -1,14 +1,14 @@ using System.Security; +using Grpc.Core; using Grpc.Net.Client; using LibplanetConsole.Common; using LibplanetConsole.Common.Extensions; using LibplanetConsole.Node; -using LibplanetConsole.Node.Services; using Microsoft.Extensions.Logging; namespace LibplanetConsole.Client; -internal sealed partial class Client : IClient, INodeCallback, IBlockChainCallback +internal sealed partial class Client : IClient { private readonly IServiceProvider _serviceProvider; private readonly SecureString _privateKey; @@ -16,6 +16,9 @@ internal sealed partial class Client : IClient, INodeCallback, IBlockChainCallba private EndPoint? _nodeEndPoint; // private RemoteNodeContext? _remoteNodeContext; private Node.Grpc.NodeGrpcService.NodeGrpcServiceClient? _remoteNode; + private Node.Grpc.BlockChainGrpcService.BlockChainGrpcServiceClient? _remoteBlockchain; + private CancellationTokenSource? _cancellationTokenSource; + private Task _callbackTask = Task.CompletedTask; private Guid _closeToken; private ClientInfo _info; @@ -81,12 +84,19 @@ public async Task StartAsync(CancellationToken cancellationToken) throw new InvalidOperationException("The client is already running."); } + // var address = $"http://{EndPointUtility.ToString(_nodeEndPoint)}"; var address = $"http://{EndPointUtility.ToString(_nodeEndPoint)}"; var channelOptions = new GrpcChannelOptions { + ThrowOperationCanceledOnCancellation = true, + MaxRetryAttempts = 1, }; - using var channel = GrpcChannel.ForAddress(address, channelOptions); + var channel = GrpcChannel.ForAddress(address, channelOptions); + // var channel2 = GrpcChannel.ForAddress(address, channelOptions); + _cancellationTokenSource = new(); _remoteNode = new Node.Grpc.NodeGrpcService.NodeGrpcServiceClient(channel); + _remoteBlockchain = new Node.Grpc.BlockChainGrpcService.BlockChainGrpcServiceClient(channel); + _callbackTask = CallbackAsync(_cancellationTokenSource.Token); // _remoteNodeContext = _serviceProvider.GetRequiredService(); // _remoteNodeContext.EndPoint = NodeEndPoint; @@ -100,6 +110,36 @@ public async Task StartAsync(CancellationToken cancellationToken) Started?.Invoke(this, EventArgs.Empty); } + private async Task CallbackAsync(CancellationToken cancellationToken) + { + try + { + await Task.Delay(2000, cancellationToken); + var callOptions = new CallOptions(deadline: DateTime.UtcNow.AddSeconds(1), cancellationToken: cancellationToken); + var response1 = await _remoteBlockchain.IsReadyAsync(new Node.Grpc.IsReadyRequest(), callOptions); + if (response1.IsReady is false) + { + throw new InvalidOperationException("The remote node is not ready."); + } + + + using var streamingCall = _remoteBlockchain.GetBlockAppendedStream( + new Node.Grpc.GetBlockAppendedStreamRequest(), + deadline: DateTime.UtcNow + TimeSpan.FromSeconds(10), + cancellationToken: cancellationToken); + + while (await streamingCall.ResponseStream.MoveNext(cancellationToken)) + { + var response = streamingCall.ResponseStream.Current; + InvokeBlockAppendedEvent(response.BlockInfo); + } + } + catch (Exception e) + { + int qwer = 0; + } + } + public async Task StopAsync(CancellationToken cancellationToken) { // if (_remoteNodeContext is null) @@ -142,14 +182,14 @@ public async ValueTask DisposeAsync() // } } - void INodeCallback.OnStarted(NodeInfo nodeInfo) => NodeInfo = nodeInfo; + // void INodeCallback.OnStarted(NodeInfo nodeInfo) => NodeInfo = nodeInfo; - void INodeCallback.OnStopped() => NodeInfo = NodeInfo.Empty; + // void INodeCallback.OnStopped() => NodeInfo = NodeInfo.Empty; - void IBlockChainCallback.OnBlockAppended(BlockInfo blockInfo) - { - BlockAppended?.Invoke(this, new BlockEventArgs(blockInfo)); - } + // void IBlockChainCallback.OnBlockAppended(BlockInfo blockInfo) + // { + // BlockAppended?.Invoke(this, new BlockEventArgs(blockInfo)); + // } private void RemoteNodeContext_Closed(object? sender, EventArgs e) { diff --git a/src/client/LibplanetConsole.Client/ClientHostedService.cs b/src/client/LibplanetConsole.Client/ClientHostedService.cs new file mode 100644 index 00000000..c06361fa --- /dev/null +++ b/src/client/LibplanetConsole.Client/ClientHostedService.cs @@ -0,0 +1,36 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace LibplanetConsole.Client; + +internal sealed class ClientHostedService( + IHostApplicationLifetime applicationLifetime, + ApplicationOptions options, + Client client, + ILogger logger) + : IHostedService +{ + public Task StartAsync(CancellationToken cancellationToken) + { + applicationLifetime.ApplicationStarted.Register(async () => + { + logger.LogInformation("Application started."); + if (options.NodeEndPoint is { } nodeEndPoint) + { + logger.LogDebug("Client auto-starting"); + // client.SeedEndPoint = nodeEndPoint; + await client.StartAsync(cancellationToken); + logger.LogDebug("Client auto-started"); + } + }); + return Task.CompletedTask; + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + if (client.IsRunning is true) + { + await client.StopAsync(cancellationToken); + } + } +} diff --git a/src/client/LibplanetConsole.Client/ClientInfoProvider.cs b/src/client/LibplanetConsole.Client/ClientInfoProvider.cs index 7b2eddd6..30f4afbb 100644 --- a/src/client/LibplanetConsole.Client/ClientInfoProvider.cs +++ b/src/client/LibplanetConsole.Client/ClientInfoProvider.cs @@ -1,11 +1,12 @@ using LibplanetConsole.Common; +using Microsoft.Extensions.Hosting; namespace LibplanetConsole.Client; internal sealed class ClientInfoProvider(Client client) - : InfoProviderBase(nameof(Client)) + : InfoProviderBase(nameof(Client)) { - protected override object? GetInfo(ApplicationBase obj) + protected override object? GetInfo(IHostApplicationLifetime obj) { return client.Info; } diff --git a/src/client/LibplanetConsole.Client/Commands/ExitCommand.cs b/src/client/LibplanetConsole.Client/Commands/ExitCommand.cs index 8c97baae..53c79aac 100644 --- a/src/client/LibplanetConsole.Client/Commands/ExitCommand.cs +++ b/src/client/LibplanetConsole.Client/Commands/ExitCommand.cs @@ -1,9 +1,23 @@ using JSSoft.Commands; +using Microsoft.Extensions.Hosting; namespace LibplanetConsole.Client.Commands; [CommandSummary("Exit the application.")] -internal sealed class ExitCommand(IApplication application) : CommandBase +internal sealed class ExitCommand(IHostApplicationLifetime applicationLifetime) + : CommandAsyncBase { - protected override void OnExecute() => application.Cancel(); + protected override async Task OnExecuteAsync(CancellationToken cancellationToken) + { + var resetEvent = new ManualResetEvent(false); + applicationLifetime.ApplicationStopping.Register(() => + { + resetEvent.Set(); + }); + applicationLifetime.StopApplication(); + while (!resetEvent.WaitOne(1)) + { + await Task.Delay(1, default); + } + } } diff --git a/src/client/LibplanetConsole.Client/Commands/InfoCommand.cs b/src/client/LibplanetConsole.Client/Commands/InfoCommand.cs index 09faa20f..6a8e0eb4 100644 --- a/src/client/LibplanetConsole.Client/Commands/InfoCommand.cs +++ b/src/client/LibplanetConsole.Client/Commands/InfoCommand.cs @@ -1,16 +1,18 @@ using JSSoft.Commands; using LibplanetConsole.Common; using LibplanetConsole.Common.Extensions; +using Microsoft.Extensions.Hosting; namespace LibplanetConsole.Client.Commands; [CommandSummary("Print client application information.")] internal sealed class InfoCommand( - IServiceProvider serviceProvider, IApplication application) : CommandBase + IServiceProvider serviceProvider, IHostApplicationLifetime applicationLifetime) + : CommandBase { protected override void OnExecute() { - var info = InfoUtility.GetInfo(serviceProvider: serviceProvider, obj: application); + var info = InfoUtility.GetInfo(serviceProvider: serviceProvider, obj: applicationLifetime); Out.WriteLineAsJson(info); } } diff --git a/src/client/LibplanetConsole.Client/IApplication.cs b/src/client/LibplanetConsole.Client/IApplication.cs index 3173c86d..8f184db8 100644 --- a/src/client/LibplanetConsole.Client/IApplication.cs +++ b/src/client/LibplanetConsole.Client/IApplication.cs @@ -1,12 +1,12 @@ -namespace LibplanetConsole.Client; +// namespace LibplanetConsole.Client; -public interface IApplication : IAsyncDisposable -{ - ApplicationInfo Info { get; } +// public interface IApplication : IAsyncDisposable +// { +// ApplicationInfo Info { get; } - Task InvokeAsync(Action action, CancellationToken cancellationToken); +// Task InvokeAsync(Action action, CancellationToken cancellationToken); - Task InvokeAsync(Func func, CancellationToken cancellationToken); +// Task InvokeAsync(Func func, CancellationToken cancellationToken); - void Cancel(); -} +// void Cancel(); +// } diff --git a/src/client/LibplanetConsole.Client/ServiceCollectionExtensions.cs b/src/client/LibplanetConsole.Client/ServiceCollectionExtensions.cs index bf063269..5f7a2b71 100644 --- a/src/client/LibplanetConsole.Client/ServiceCollectionExtensions.cs +++ b/src/client/LibplanetConsole.Client/ServiceCollectionExtensions.cs @@ -10,10 +10,15 @@ public static class ServiceCollectionExtensions public static IServiceCollection AddClient( this IServiceCollection @this, ApplicationOptions options) { + var synchronizationContext = SynchronizationContext.Current ?? new(); + SynchronizationContext.SetSynchronizationContext(synchronizationContext); + @this.AddSingleton(synchronizationContext); + @this.AddSingleton(options); + @this.AddSingleton(s => new Client(s, options)) .AddSingleton(s => s.GetRequiredService()) .AddSingleton(s => s.GetRequiredService()); - + @this.AddHostedService(); // @this.AddSingleton() // .AddSingleton(s => s.GetRequiredService()) // .AddSingleton(s => s.GetRequiredService()); diff --git a/src/client/LibplanetConsole.Client/Services/ClientService.cs b/src/client/LibplanetConsole.Client/Services/ClientService.cs deleted file mode 100644 index 431fe863..00000000 --- a/src/client/LibplanetConsole.Client/Services/ClientService.cs +++ /dev/null @@ -1,49 +0,0 @@ -// using LibplanetConsole.Common; -// using LibplanetConsole.Common.Actions; -// using LibplanetConsole.Common.Services; - -// namespace LibplanetConsole.Client.Services; - -// internal sealed class ClientService : LocalService, IClientService -// { -// private readonly Client _client; - -// public ClientService(Client client) -// { -// _client = client; -// _client.Started += (s, e) => Callback.OnStarted(_client.Info); -// _client.Stopped += (s, e) => Callback.OnStopped(); -// } - -// public async Task GetInfoAsync(CancellationToken cancellationToken) -// { -// await Task.CompletedTask; -// return _client.Info; -// } - -// public async Task StartAsync( -// string nodeEndPoint, CancellationToken cancellationToken) -// { -// _client.NodeEndPoint = EndPointUtility.Parse(nodeEndPoint); -// await _client.StartAsync(cancellationToken); -// return _client.Info; -// } - -// public Task StopAsync(CancellationToken cancellationToken) -// => _client.StopAsync(cancellationToken); - -// public async Task SendTransactionAsync( -// TransactionOptions transactionOptions, CancellationToken cancellationToken) -// { -// if (transactionOptions.TryVerify(_client) == true) -// { -// var action = new StringAction -// { -// Value = transactionOptions.Text, -// }; -// return await _client.SendTransactionAsync([action], cancellationToken); -// } - -// throw new InvalidOperationException("The signature is invalid."); -// } -// } diff --git a/src/client/LibplanetConsole.Client/Services/ClientServiceContext.cs b/src/client/LibplanetConsole.Client/Services/ClientServiceContext.cs deleted file mode 100644 index 94f99a07..00000000 --- a/src/client/LibplanetConsole.Client/Services/ClientServiceContext.cs +++ /dev/null @@ -1,8 +0,0 @@ -// using LibplanetConsole.Common.Services; - -// namespace LibplanetConsole.Client.Services; - -// internal sealed class ClientServiceContext( -// IEnumerable localServices) : LocalServiceContext([.. localServices]) -// { -// } diff --git a/src/client/LibplanetConsole.Client/Services/RemoteBlockChainService.cs b/src/client/LibplanetConsole.Client/Services/RemoteBlockChainService.cs deleted file mode 100644 index 68a2a40d..00000000 --- a/src/client/LibplanetConsole.Client/Services/RemoteBlockChainService.cs +++ /dev/null @@ -1,9 +0,0 @@ -// using LibplanetConsole.Common.Services; -// using LibplanetConsole.Node.Services; - -// namespace LibplanetConsole.Client.Services; - -// internal sealed class RemoteBlockChainService(Client client) -// : RemoteService(client) -// { -// } diff --git a/src/client/LibplanetConsole.Client/Services/RemoteNodeContext.cs b/src/client/LibplanetConsole.Client/Services/RemoteNodeContext.cs deleted file mode 100644 index 44beebd3..00000000 --- a/src/client/LibplanetConsole.Client/Services/RemoteNodeContext.cs +++ /dev/null @@ -1,9 +0,0 @@ -// using LibplanetConsole.Common.Services; - -// namespace LibplanetConsole.Client.Services; - -// internal sealed class RemoteNodeContext( -// IEnumerable remoteServices) -// : RemoteServiceContext([.. remoteServices]) -// { -// } diff --git a/src/client/LibplanetConsole.Client/Services/RemoteNodeService.cs b/src/client/LibplanetConsole.Client/Services/RemoteNodeService.cs deleted file mode 100644 index 49a61a86..00000000 --- a/src/client/LibplanetConsole.Client/Services/RemoteNodeService.cs +++ /dev/null @@ -1,9 +0,0 @@ -// using LibplanetConsole.Common.Services; -// using LibplanetConsole.Node.Services; - -// namespace LibplanetConsole.Client.Services; - -// internal sealed class RemoteNodeService(Client client) -// : RemoteService(client) -// { -// } diff --git a/src/console/LibplanetConsole.Console/Node.BlockChain.cs b/src/console/LibplanetConsole.Console/Node.BlockChain.cs index 5f5a42a6..cc7a7764 100644 --- a/src/console/LibplanetConsole.Console/Node.BlockChain.cs +++ b/src/console/LibplanetConsole.Console/Node.BlockChain.cs @@ -1,6 +1,5 @@ using System.Security.Cryptography; using LibplanetConsole.Node; -using LibplanetConsole.Node.Services; namespace LibplanetConsole.Console; @@ -42,11 +41,11 @@ public Task SendTransactionAsync( throw new NotImplementedException(); } - void IBlockChainCallback.OnBlockAppended(BlockInfo blockInfo) - { - _nodeInfo = _nodeInfo with { TipHash = blockInfo.Hash }; - BlockAppended?.Invoke(this, new BlockEventArgs(blockInfo)); - } + // void IBlockChainCallback.OnBlockAppended(BlockInfo blockInfo) + // { + // _nodeInfo = _nodeInfo with { TipHash = blockInfo.Hash }; + // BlockAppended?.Invoke(this, new BlockEventArgs(blockInfo)); + // } public Task GetTipHashAsync(CancellationToken cancellationToken) { diff --git a/src/console/LibplanetConsole.Console/Node.cs b/src/console/LibplanetConsole.Console/Node.cs index 12b7a130..60c8c487 100644 --- a/src/console/LibplanetConsole.Console/Node.cs +++ b/src/console/LibplanetConsole.Console/Node.cs @@ -2,13 +2,12 @@ using LibplanetConsole.Common.Exceptions; using LibplanetConsole.Common.Extensions; using LibplanetConsole.Node; -using LibplanetConsole.Node.Services; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace LibplanetConsole.Console; -internal sealed partial class Node : INode, IBlockChain, INodeCallback, IBlockChainCallback +internal sealed partial class Node : INode, IBlockChain { private static readonly Codec _codec = new(); private readonly IServiceProvider _serviceProvider; @@ -222,25 +221,25 @@ public Task StartProcessAsync(AddNewNodeOptions options, CancellationToken cance return Task.CompletedTask; } - void INodeCallback.OnStarted(NodeInfo nodeInfo) - { - if (_isInProgress != true) - { - _nodeInfo = nodeInfo; - IsRunning = true; - Started?.Invoke(this, EventArgs.Empty); - } - } + // void INodeCallback.OnStarted(NodeInfo nodeInfo) + // { + // if (_isInProgress != true) + // { + // _nodeInfo = nodeInfo; + // IsRunning = true; + // Started?.Invoke(this, EventArgs.Empty); + // } + // } - void INodeCallback.OnStopped() - { - if (_isInProgress != true) - { - _nodeInfo = NodeInfo.Empty; - IsRunning = false; - Stopped?.Invoke(this, EventArgs.Empty); - } - } + // void INodeCallback.OnStopped() + // { + // if (_isInProgress != true) + // { + // _nodeInfo = NodeInfo.Empty; + // IsRunning = false; + // Stopped?.Invoke(this, EventArgs.Empty); + // } + // } // private static IEnumerable GetRemoteServices( // IServiceProvider serviceProvider) diff --git a/src/node/LibplanetConsole.Node/Services/BlockChainGrpcServiceV1.cs b/src/node/LibplanetConsole.Node/Services/BlockChainGrpcServiceV1.cs index 0936789c..201835e1 100644 --- a/src/node/LibplanetConsole.Node/Services/BlockChainGrpcServiceV1.cs +++ b/src/node/LibplanetConsole.Node/Services/BlockChainGrpcServiceV1.cs @@ -1,13 +1,22 @@ using Grpc.Core; using LibplanetConsole.Node.Grpc; +using Microsoft.Extensions.Hosting; namespace LibplanetConsole.Node.Services; -internal sealed class BlockChainGrpcServiceV1(Node node, IBlockChain blockChain) +internal sealed class BlockChainGrpcServiceV1( + Node node, + IBlockChain blockChain, + IHostApplicationLifetime applicationLifetime) : BlockChainGrpcService.BlockChainGrpcServiceBase { private static readonly Codec _codec = new(); + public override Task IsReady(IsReadyRequest request, ServerCallContext context) + { + return Task.Run(() => new IsReadyResponse { IsReady = node.IsRunning }); + } + public async override Task SendTransaction( SendTransactionRequest request, ServerCallContext context) { @@ -60,4 +69,42 @@ public override async Task GetAction( var action = await node.GetActionAsync(txId, actionIndex, context.CancellationToken); return new GetActionResponse { ActionData = Google.Protobuf.ByteString.CopyFrom(action) }; } + + public override async Task GetBlockAppendedStream( + GetBlockAppendedStreamRequest request, + IServerStreamWriter responseStream, + ServerCallContext context) + { + var blockChainCallback = new BlockAppendedCallback(responseStream, blockChain); + using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource( + context.CancellationToken); + applicationLifetime.ApplicationStopping.Register(cancellationTokenSource.Cancel); + await blockChainCallback.RunAsync(cancellationTokenSource.Token); + } + + private sealed class BlockAppendedCallback( + IAsyncStreamWriter streamWriter, IBlockChain blockChain) + : Callback(streamWriter) + { + protected override async Task OnRun(CancellationToken cancellationToken) + { + blockChain.BlockAppended += BlockChain_BlockAppended; + try + { + await base.OnRun(cancellationToken); + } + finally + { + blockChain.BlockAppended -= BlockChain_BlockAppended; + } + } + + private async void BlockChain_BlockAppended(object? sender, BlockEventArgs e) + { + await InvokeAsync(new GetBlockAppendedStreamResponse + { + BlockInfo = e.BlockInfo, + }); + } + } } diff --git a/src/node/LibplanetConsole.Node/Services/Callback.cs b/src/node/LibplanetConsole.Node/Services/Callback.cs new file mode 100644 index 00000000..2c4602bf --- /dev/null +++ b/src/node/LibplanetConsole.Node/Services/Callback.cs @@ -0,0 +1,47 @@ +using Grpc.Core; + +namespace LibplanetConsole.Node.Services; + +internal abstract class Callback(IAsyncStreamWriter streamWriter) +{ + private CancellationTokenSource? _cancellationTokenSource; + + public async Task RunAsync(CancellationToken cancellationToken) + { + if (_cancellationTokenSource is not null) + { + throw new InvalidOperationException("Callback is already running."); + } + + using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource( + cancellationToken); + + _cancellationTokenSource = cancellationTokenSource; + try + { + await OnRun(cancellationTokenSource.Token); + } + finally + { + _cancellationTokenSource = null; + } + } + + protected virtual async Task OnRun(CancellationToken cancellationToken) + { + while (cancellationToken.IsCancellationRequested is false) + { + await Task.Delay(1, default); + } + } + + protected async Task InvokeAsync(T value) + { + if (_cancellationTokenSource is null) + { + throw new InvalidOperationException("Callback is not running."); + } + + await streamWriter.WriteAsync(value, _cancellationTokenSource.Token); + } +} diff --git a/src/shared/LibplanetConsole.Node/BlockInfo.Node.cs b/src/shared/LibplanetConsole.Node/BlockInfo.Node.cs index 4ca85eb6..317d6ec0 100644 --- a/src/shared/LibplanetConsole.Node/BlockInfo.Node.cs +++ b/src/shared/LibplanetConsole.Node/BlockInfo.Node.cs @@ -13,7 +13,6 @@ public BlockInfo(BlockChain blockChain, Block block) Height = block.Index; Hash = block.Hash; Miner = block.Miner; - Transactions = [.. block.Transactions.Select(GetTransaction)]; TransactionInfo GetTransaction(Transaction transaction) { diff --git a/src/shared/LibplanetConsole.Node/BlockInfo.cs b/src/shared/LibplanetConsole.Node/BlockInfo.cs index e5800f3e..8718dd33 100644 --- a/src/shared/LibplanetConsole.Node/BlockInfo.cs +++ b/src/shared/LibplanetConsole.Node/BlockInfo.cs @@ -1,3 +1,5 @@ +using GrpcBlockInfo = LibplanetConsole.Node.Grpc.BlockInfo; + namespace LibplanetConsole.Node; public readonly partial record struct BlockInfo @@ -12,5 +14,23 @@ public BlockInfo() public Address Miner { get; init; } - public TransactionInfo[] Transactions { get; init; } = []; + public static implicit operator BlockInfo(GrpcBlockInfo blockInfo) + { + return new BlockInfo + { + Height = blockInfo.Height, + Hash = BlockHash.FromString(blockInfo.Hash), + Miner = new Address(blockInfo.Miner), + }; + } + + public static implicit operator GrpcBlockInfo(BlockInfo blockInfo) + { + return new GrpcBlockInfo + { + Height = blockInfo.Height, + Hash = blockInfo.Hash.ToString(), + Miner = blockInfo.Miner.ToHex(), + }; + } } diff --git a/src/shared/LibplanetConsole.Node/Protos/BlockChainGrpcService.proto b/src/shared/LibplanetConsole.Node/Protos/BlockChainGrpcService.proto index ea213b9a..5f4962a2 100644 --- a/src/shared/LibplanetConsole.Node/Protos/BlockChainGrpcService.proto +++ b/src/shared/LibplanetConsole.Node/Protos/BlockChainGrpcService.proto @@ -5,60 +5,83 @@ option csharp_namespace = "LibplanetConsole.Node.Grpc"; package libplanet.console.node.v1; service BlockChainGrpcService { + rpc IsReady(IsReadyRequest) returns (IsReadyResponse); rpc SendTransaction(SendTransactionRequest) returns (SendTransactionResponse); rpc GetNextNonce(GetNextNonceRequest) returns (GetNextNonceResponse); rpc GetTipHash(GetTipHashRequest) returns (GetTipHashResponse); rpc GetState(GetStateRequest) returns (GetStateResponse); rpc GetBlockHash(GetBlockHashRequest) returns (GetBlockHashResponse); rpc GetAction(GetActionRequest) returns (GetActionResponse); + + rpc GetBlockAppendedStream(GetBlockAppendedStreamRequest) returns (stream GetBlockAppendedStreamResponse); +} + +message BlockInfo { + int64 height = 1; + string hash = 2; + string miner = 3; +} + +message IsReadyRequest { +} + +message IsReadyResponse { + bool isReady = 1; } message SendTransactionRequest { - bytes transactionData = 1; - } - - message SendTransactionResponse { - string txId = 1; - } - - message GetNextNonceRequest { - string address = 1; - } - - message GetNextNonceResponse { - int64 nonce = 1; - } - - message GetTipHashRequest { - } - - message GetTipHashResponse { - string blockHash = 1; - } - - message GetStateRequest { - optional string blockHash = 1; - string accountAddress = 2; - string address = 3; - } - - message GetStateResponse { - bytes stateData = 1; - } - - message GetBlockHashRequest { - int64 height = 1; - } - - message GetBlockHashResponse { - string blockHash = 1; - } - - message GetActionRequest { - string txId = 1; - int32 actionIndex = 2; - } - - message GetActionResponse { - bytes actionData = 1; - } + bytes transactionData = 1; +} + +message SendTransactionResponse { + string txId = 1; +} + +message GetNextNonceRequest { + string address = 1; +} + +message GetNextNonceResponse { + int64 nonce = 1; +} + +message GetTipHashRequest { +} + +message GetTipHashResponse { + string blockHash = 1; +} + +message GetStateRequest { + optional string blockHash = 1; + string accountAddress = 2; + string address = 3; +} + +message GetStateResponse { + bytes stateData = 1; +} + +message GetBlockHashRequest { + int64 height = 1; +} + +message GetBlockHashResponse { + string blockHash = 1; +} + +message GetActionRequest { + string txId = 1; + int32 actionIndex = 2; +} + +message GetActionResponse { + bytes actionData = 1; +} + +message GetBlockAppendedStreamRequest { +} + +message GetBlockAppendedStreamResponse { + BlockInfo blockInfo = 1; +} diff --git a/src/shared/LibplanetConsole.Node/Services/IBlockChainCallback.cs b/src/shared/LibplanetConsole.Node/Services/IBlockChainCallback.cs deleted file mode 100644 index 301d93b2..00000000 --- a/src/shared/LibplanetConsole.Node/Services/IBlockChainCallback.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace LibplanetConsole.Node.Services; - -public interface IBlockChainCallback -{ - void OnBlockAppended(BlockInfo blockInfo); -} diff --git a/src/shared/LibplanetConsole.Node/Services/IBlockChainService.cs b/src/shared/LibplanetConsole.Node/Services/IBlockChainService.cs deleted file mode 100644 index edef27c6..00000000 --- a/src/shared/LibplanetConsole.Node/Services/IBlockChainService.cs +++ /dev/null @@ -1,28 +0,0 @@ -// using System.Security.Cryptography; - -// namespace LibplanetConsole.Node.Services; - -// public interface IBlockChainService -// { -// Task SendTransactionAsync(byte[] transaction, CancellationToken cancellationToken); - -// Task GetNextNonceAsync(Address address, CancellationToken cancellationToken); - -// Task GetTipHashAsync(CancellationToken cancellationToken); - -// Task GetStateAsync( -// BlockHash? blockHash, -// Address accountAddress, -// Address address, -// CancellationToken cancellationToken); - -// Task GetStateByStateRootHashAsync( -// HashDigest stateRootHash, -// Address accountAddress, -// Address address, -// CancellationToken cancellationToken); - -// Task GetBlockHashAsync(long height, CancellationToken cancellationToken); - -// Task GetActionAsync(TxId txId, int actionIndex, CancellationToken cancellationToken); -// } diff --git a/src/shared/LibplanetConsole.Node/Services/INodeCallback.cs b/src/shared/LibplanetConsole.Node/Services/INodeCallback.cs deleted file mode 100644 index 834d8ed5..00000000 --- a/src/shared/LibplanetConsole.Node/Services/INodeCallback.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace LibplanetConsole.Node.Services; - -public interface INodeCallback -{ - void OnStarted(NodeInfo nodeInfo); - - void OnStopped(); -} diff --git a/src/shared/LibplanetConsole.Node/Services/INodeService.cs b/src/shared/LibplanetConsole.Node/Services/INodeService.cs deleted file mode 100644 index d03687b4..00000000 --- a/src/shared/LibplanetConsole.Node/Services/INodeService.cs +++ /dev/null @@ -1,10 +0,0 @@ -// namespace LibplanetConsole.Node.Services; - -// public interface INodeService -// { -// Task StartAsync(string seedEndPoint, CancellationToken cancellationToken); - -// Task StopAsync(CancellationToken cancellationToken); - -// Task GetInfoAsync(CancellationToken cancellationToken); -// } From b2914542cf8fef99618506872dcadb71207278f7 Mon Sep 17 00:00:00 2001 From: s2quake Date: Sun, 6 Oct 2024 21:17:04 +0900 Subject: [PATCH 07/18] feat: Add the streamer that sends data to the client when an event occurs --- src/client/LibplanetConsole.Client/Client.cs | 34 +++++---- .../Services/BlockChainGrpcServiceV1.cs | 37 ++-------- .../Services/EventStreamer.cs | 69 +++++++++++++++++++ .../Services/NodeGrpcServiceV1.cs | 30 +++++++- .../Services/{Callback.cs => Streamer.cs} | 14 +++- .../Protos/NodeGrpcService.proto | 36 +++++++--- 6 files changed, 158 insertions(+), 62 deletions(-) create mode 100644 src/node/LibplanetConsole.Node/Services/EventStreamer.cs rename src/node/LibplanetConsole.Node/Services/{Callback.cs => Streamer.cs} (68%) diff --git a/src/client/LibplanetConsole.Client/Client.cs b/src/client/LibplanetConsole.Client/Client.cs index 3327cf51..131255e8 100644 --- a/src/client/LibplanetConsole.Client/Client.cs +++ b/src/client/LibplanetConsole.Client/Client.cs @@ -4,6 +4,7 @@ using LibplanetConsole.Common; using LibplanetConsole.Common.Extensions; using LibplanetConsole.Node; +using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Logging; namespace LibplanetConsole.Client; @@ -14,9 +15,9 @@ internal sealed partial class Client : IClient private readonly SecureString _privateKey; private readonly ILogger _logger; private EndPoint? _nodeEndPoint; - // private RemoteNodeContext? _remoteNodeContext; private Node.Grpc.NodeGrpcService.NodeGrpcServiceClient? _remoteNode; private Node.Grpc.BlockChainGrpcService.BlockChainGrpcServiceClient? _remoteBlockchain; + private GrpcChannel? _channel; private CancellationTokenSource? _cancellationTokenSource; private Task _callbackTask = Task.CompletedTask; private Guid _closeToken; @@ -67,37 +68,32 @@ public EndPoint NodeEndPoint public bool IsRunning { get; private set; } - // private INodeService RemoteNodeService - // => _serviceProvider.GetRequiredService().Service; - - // private IBlockChainService RemoteBlockChainService - // => _serviceProvider.GetRequiredService().Service; - public override string ToString() => $"[{Address}]"; public bool Verify(object obj, byte[] signature) => PublicKey.Verify(obj, signature); public async Task StartAsync(CancellationToken cancellationToken) { - if (_remoteNode is not null) + if (_channel is not null) { throw new InvalidOperationException("The client is already running."); } - // var address = $"http://{EndPointUtility.ToString(_nodeEndPoint)}"; var address = $"http://{EndPointUtility.ToString(_nodeEndPoint)}"; var channelOptions = new GrpcChannelOptions { ThrowOperationCanceledOnCancellation = true, MaxRetryAttempts = 1, }; - var channel = GrpcChannel.ForAddress(address, channelOptions); - // var channel2 = GrpcChannel.ForAddress(address, channelOptions); + HubConnectionContext + _channel = GrpcChannel.ForAddress(address, channelOptions); _cancellationTokenSource = new(); - _remoteNode = new Node.Grpc.NodeGrpcService.NodeGrpcServiceClient(channel); - _remoteBlockchain = new Node.Grpc.BlockChainGrpcService.BlockChainGrpcServiceClient(channel); + _remoteNode = new Node.Grpc.NodeGrpcService.NodeGrpcServiceClient(_channel); + _remoteBlockchain = new Node.Grpc.BlockChainGrpcService.BlockChainGrpcServiceClient(_channel); _callbackTask = CallbackAsync(_cancellationTokenSource.Token); + _channel.Target + // _remoteNodeContext = _serviceProvider.GetRequiredService(); // _remoteNodeContext.EndPoint = NodeEndPoint; // _closeToken = await _remoteNodeContext.OpenAsync(cancellationToken); @@ -122,7 +118,6 @@ private async Task CallbackAsync(CancellationToken cancellationToken) throw new InvalidOperationException("The remote node is not ready."); } - using var streamingCall = _remoteBlockchain.GetBlockAppendedStream( new Node.Grpc.GetBlockAppendedStreamRequest(), deadline: DateTime.UtcNow + TimeSpan.FromSeconds(10), @@ -142,15 +137,18 @@ private async Task CallbackAsync(CancellationToken cancellationToken) public async Task StopAsync(CancellationToken cancellationToken) { - // if (_remoteNodeContext is null) - // { - // throw new InvalidOperationException("The client is not running."); - // } + if (_channel is null) + { + throw new InvalidOperationException("The client is not running."); + } // _remoteNodeContext.Closed -= RemoteNodeContext_Closed; // await _remoteNodeContext.CloseAsync(_closeToken, cancellationToken); // _info = _info with { NodeAddress = default }; // _remoteNodeContext = null; + await _channel.ShutdownAsync(); + _channel.Dispose(); + _channel = null; _closeToken = Guid.Empty; IsRunning = false; _logger.LogDebug("Client is stopped: {Address}", Address); diff --git a/src/node/LibplanetConsole.Node/Services/BlockChainGrpcServiceV1.cs b/src/node/LibplanetConsole.Node/Services/BlockChainGrpcServiceV1.cs index 201835e1..6549173a 100644 --- a/src/node/LibplanetConsole.Node/Services/BlockChainGrpcServiceV1.cs +++ b/src/node/LibplanetConsole.Node/Services/BlockChainGrpcServiceV1.cs @@ -75,36 +75,11 @@ public override async Task GetBlockAppendedStream( IServerStreamWriter responseStream, ServerCallContext context) { - var blockChainCallback = new BlockAppendedCallback(responseStream, blockChain); - using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource( - context.CancellationToken); - applicationLifetime.ApplicationStopping.Register(cancellationTokenSource.Cancel); - await blockChainCallback.RunAsync(cancellationTokenSource.Token); - } - - private sealed class BlockAppendedCallback( - IAsyncStreamWriter streamWriter, IBlockChain blockChain) - : Callback(streamWriter) - { - protected override async Task OnRun(CancellationToken cancellationToken) - { - blockChain.BlockAppended += BlockChain_BlockAppended; - try - { - await base.OnRun(cancellationToken); - } - finally - { - blockChain.BlockAppended -= BlockChain_BlockAppended; - } - } - - private async void BlockChain_BlockAppended(object? sender, BlockEventArgs e) - { - await InvokeAsync(new GetBlockAppendedStreamResponse - { - BlockInfo = e.BlockInfo, - }); - } + var streamer = new EventStreamer( + responseStream, + attach: handler => blockChain.BlockAppended += handler, + detach: handler => blockChain.BlockAppended -= handler, + selector: e => new GetBlockAppendedStreamResponse { BlockInfo = e.BlockInfo }); + await streamer.RunAsync(applicationLifetime, context.CancellationToken); } } diff --git a/src/node/LibplanetConsole.Node/Services/EventStreamer.cs b/src/node/LibplanetConsole.Node/Services/EventStreamer.cs new file mode 100644 index 00000000..697b1a0c --- /dev/null +++ b/src/node/LibplanetConsole.Node/Services/EventStreamer.cs @@ -0,0 +1,69 @@ +#pragma warning disable SA1402 // File may only contain a single type +using Grpc.Core; + +namespace LibplanetConsole.Node.Services; + +internal sealed class EventStreamer( + IAsyncStreamWriter streamWriter, + Action> attach, + Action> detach, + Func selector) : Streamer(streamWriter) +{ + protected async override Task OnRun(CancellationToken cancellationToken) + { + void Handler(object? s, TEventArgs args) + { + var value = selector(args); + WriteValue(value); + } + + attach(Handler); + try + { + await base.OnRun(cancellationToken); + } + finally + { + detach(Handler); + } + } +} + +internal sealed class EventStreamer( + IAsyncStreamWriter streamWriter, + Action attach, + Action detach, + Func selector) : Streamer(streamWriter) +{ + public EventStreamer( + IAsyncStreamWriter streamWriter, + Action attach, + Action detach) + : this(streamWriter, attach, detach, CreateInstanceBinder) + { + } + + protected async override Task OnRun(CancellationToken cancellationToken) + { + void Handler(object? s, EventArgs args) + { + var value = selector(); + WriteValue(value); + } + + attach(Handler); + try + { + await base.OnRun(cancellationToken); + } + finally + { + detach(Handler); + } + } + + private static TResponse CreateInstanceBinder() + => Activator.CreateInstance() + ?? throw new InvalidOperationException( + "Failed to create an instance of TResponse."); +} diff --git a/src/node/LibplanetConsole.Node/Services/NodeGrpcServiceV1.cs b/src/node/LibplanetConsole.Node/Services/NodeGrpcServiceV1.cs index 73e74cbc..c400ca5a 100644 --- a/src/node/LibplanetConsole.Node/Services/NodeGrpcServiceV1.cs +++ b/src/node/LibplanetConsole.Node/Services/NodeGrpcServiceV1.cs @@ -1,9 +1,12 @@ using Grpc.Core; using LibplanetConsole.Node.Grpc; +using Microsoft.Extensions.Hosting; namespace LibplanetConsole.Node.Services; -public sealed class NodeGrpcServiceV1(INode node) : NodeGrpcService.NodeGrpcServiceBase +internal sealed class NodeGrpcServiceV1( + IHostApplicationLifetime applicationLifetime, INode node) + : NodeGrpcService.NodeGrpcServiceBase { public override async Task Start(StartRequest request, ServerCallContext context) { @@ -26,4 +29,29 @@ public override Task GetInfo(GetInfoRequest request, ServerCall return Task.Run(Action, context.CancellationToken); } + + public override async Task GetStartedStream( + GetStartedStreamRequest request, + IServerStreamWriter responseStream, + ServerCallContext context) + { + var streamer = new EventStreamer( + responseStream, + handler => node.Started += handler, + handler => node.Started -= handler, + () => new GetStartedStreamResponse { NodeInfo = node.Info }); + await streamer.RunAsync(applicationLifetime, context.CancellationToken); + } + + public override async Task GetStoppedStream( + GetStoppedStreamRequest request, + IServerStreamWriter responseStream, + ServerCallContext context) + { + var streamer = new EventStreamer( + responseStream, + handler => node.Stopped += handler, + handler => node.Stopped -= handler); + await streamer.RunAsync(applicationLifetime, context.CancellationToken); + } } diff --git a/src/node/LibplanetConsole.Node/Services/Callback.cs b/src/node/LibplanetConsole.Node/Services/Streamer.cs similarity index 68% rename from src/node/LibplanetConsole.Node/Services/Callback.cs rename to src/node/LibplanetConsole.Node/Services/Streamer.cs index 2c4602bf..439680ab 100644 --- a/src/node/LibplanetConsole.Node/Services/Callback.cs +++ b/src/node/LibplanetConsole.Node/Services/Streamer.cs @@ -1,8 +1,9 @@ using Grpc.Core; +using Microsoft.Extensions.Hosting; namespace LibplanetConsole.Node.Services; -internal abstract class Callback(IAsyncStreamWriter streamWriter) +internal abstract class Streamer(IAsyncStreamWriter streamWriter) { private CancellationTokenSource? _cancellationTokenSource; @@ -27,6 +28,15 @@ public async Task RunAsync(CancellationToken cancellationToken) } } + public async Task RunAsync( + IHostApplicationLifetime applicationLifetime, CancellationToken cancellationToken) + { + using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource( + cancellationToken); + applicationLifetime.ApplicationStopping.Register(cancellationTokenSource.Cancel); + await RunAsync(cancellationTokenSource.Token); + } + protected virtual async Task OnRun(CancellationToken cancellationToken) { while (cancellationToken.IsCancellationRequested is false) @@ -35,7 +45,7 @@ protected virtual async Task OnRun(CancellationToken cancellationToken) } } - protected async Task InvokeAsync(T value) + protected async void WriteValue(T value) { if (_cancellationTokenSource is null) { diff --git a/src/shared/LibplanetConsole.Node/Protos/NodeGrpcService.proto b/src/shared/LibplanetConsole.Node/Protos/NodeGrpcService.proto index f705636a..69f6ddf1 100644 --- a/src/shared/LibplanetConsole.Node/Protos/NodeGrpcService.proto +++ b/src/shared/LibplanetConsole.Node/Protos/NodeGrpcService.proto @@ -8,25 +8,28 @@ service NodeGrpcService { rpc Start(StartRequest) returns (StartResponse); rpc Stop(StopRequest) returns (StopResponse); rpc GetInfo(GetInfoRequest) returns (GetInfoResponse); + + rpc GetStartedStream(GetStartedStreamRequest) returns (stream GetStartedStreamResponse); + rpc GetStoppedStream(GetStoppedStreamRequest) returns (stream GetStoppedStreamResponse); } message NodeInfo { - int32 processId = 1; - string appProtocolVersion = 2; - string swarmEndPoint = 3; - string consensusEndPoint = 4; + int32 process_id = 1; + string app_protocol_version = 2; + string swarm_end_point = 3; + string consensus_end_point = 4; string address = 5; - string genesisHash = 6; - string tipHash = 7; - bool isRunning = 8; + string genesis_hash = 6; + string tip_hash = 7; + bool is_running = 8; } message StartRequest { - string seedEndPoint = 1; + string seed_end_point = 1; } message StartResponse { - NodeInfo nodeInfo = 1; + NodeInfo node_info = 1; } message StopRequest { @@ -39,5 +42,18 @@ message GetInfoRequest { } message GetInfoResponse { - NodeInfo nodeInfo = 1; + NodeInfo node_info = 1; +} + +message GetStartedStreamRequest { +} + +message GetStartedStreamResponse { + NodeInfo node_info = 1; +} + +message GetStoppedStreamRequest { +} + +message GetStoppedStreamResponse { } From 27f26e5397f512d473dea814bb75834e40689759 Mon Sep 17 00:00:00 2001 From: s2quake Date: Tue, 8 Oct 2024 09:57:45 +0900 Subject: [PATCH 08/18] refactor: Client run --- .../ApplicationBase.cs | 95 ----------- .../ApplicationInfoProvider.cs | 4 +- .../Client.BlockChain.cs | 157 +++++++++++++----- src/client/LibplanetConsole.Client/Client.cs | 138 +++++++-------- .../ClientHostedService.cs | 3 +- .../Grpc/BlockChainService.cs | 47 ++++++ .../Grpc/Connection.cs | 10 ++ .../Grpc/NodeService.cs | 92 ++++++++++ .../LibplanetConsole.Client/IApplication.cs | 12 -- .../LibplanetConsole.Client.csproj | 1 + .../ServiceCollectionExtensions.cs | 27 +-- .../LibplanetConsole.Settings.csproj | 24 +-- .../LibplanetConsole.Node.csproj | 1 + .../Services/NodeGrpcServiceV1.cs | 5 + .../ConnectionMonitor.cs | 36 ++++ .../LibplanetConsole.Grpc}/EventStreamer.cs | 10 +- src/shared/LibplanetConsole.Grpc/RunTask.cs | 83 +++++++++ .../LibplanetConsole.Grpc/StreamReceiver.cs | 37 +++++ .../LibplanetConsole.Grpc}/Streamer.cs | 10 +- .../Protos/BlockChainGrpcService.proto | 29 ++-- .../Protos/NodeGrpcService.proto | 7 + 21 files changed, 549 insertions(+), 279 deletions(-) delete mode 100644 src/client/LibplanetConsole.Client/ApplicationBase.cs create mode 100644 src/client/LibplanetConsole.Client/Grpc/BlockChainService.cs create mode 100644 src/client/LibplanetConsole.Client/Grpc/Connection.cs create mode 100644 src/client/LibplanetConsole.Client/Grpc/NodeService.cs delete mode 100644 src/client/LibplanetConsole.Client/IApplication.cs create mode 100644 src/shared/LibplanetConsole.Grpc/ConnectionMonitor.cs rename src/{node/LibplanetConsole.Node/Services => shared/LibplanetConsole.Grpc}/EventStreamer.cs (87%) create mode 100644 src/shared/LibplanetConsole.Grpc/RunTask.cs create mode 100644 src/shared/LibplanetConsole.Grpc/StreamReceiver.cs rename src/{node/LibplanetConsole.Node/Services => shared/LibplanetConsole.Grpc}/Streamer.cs (86%) diff --git a/src/client/LibplanetConsole.Client/ApplicationBase.cs b/src/client/LibplanetConsole.Client/ApplicationBase.cs deleted file mode 100644 index 6e42b3a5..00000000 --- a/src/client/LibplanetConsole.Client/ApplicationBase.cs +++ /dev/null @@ -1,95 +0,0 @@ -// using System.Diagnostics; -// using LibplanetConsole.Client.Services; -// using LibplanetConsole.Common.Extensions; -// using LibplanetConsole.Framework; -// using Microsoft.Extensions.DependencyInjection; -// using Microsoft.Extensions.Logging; - -// namespace LibplanetConsole.Client; - -// public abstract class ApplicationBase : ApplicationFramework, IApplication -// { -// private readonly IServiceProvider _serviceProvider; -// private readonly Client _client; -// private readonly Process? _parentProcess; -// private readonly ApplicationInfo _info; -// private readonly ILogger _logger; -// private readonly Task _waitForExitTask = Task.CompletedTask; -// // private ClientServiceContext? _clientServiceContext; -// private Guid _closeToken; - -// protected ApplicationBase(IServiceProvider serviceProvider, ApplicationOptions options) -// : base(serviceProvider) -// { -// _serviceProvider = serviceProvider; -// _logger = serviceProvider.GetLogger(); -// _logger.LogDebug(Environment.CommandLine); -// _logger.LogDebug("Application initializing..."); -// _client = serviceProvider.GetRequiredService(); -// _info = new() -// { -// EndPoint = options.EndPoint, -// NodeEndPoint = options.NodeEndPoint, -// LogPath = options.LogPath, -// }; -// if (options.ParentProcessId != 0 && -// Process.GetProcessById(options.ParentProcessId) is { } parentProcess) -// { -// _parentProcess = parentProcess; -// _waitForExitTask = WaitForExit(parentProcess, Cancel); -// } - -// _logger.LogDebug("Application initialized."); -// } - -// public EndPoint EndPoint => _info.EndPoint; - -// public ApplicationInfo Info => _info; - -// protected override bool CanClose => _parentProcess?.HasExited == true; - -// public override object? GetService(Type serviceType) -// => _serviceProvider.GetService(serviceType); - -// protected override async Task OnRunAsync(CancellationToken cancellationToken) -// { -// _logger.LogDebug("ClientServiceContext is starting: {EndPoint}", _info.EndPoint); -// // _clientServiceContext = _serviceProvider.GetRequiredService(); -// // _clientServiceContext.EndPoint = _info.EndPoint; -// // _closeToken = await _clientServiceContext.StartAsync(cancellationToken: default); -// _logger.LogDebug("ClientServiceContext is started: {EndPoint}", _info.EndPoint); -// await base.OnRunAsync(cancellationToken); -// await AutoStartAsync(cancellationToken); -// } - -// protected override async ValueTask OnDisposeAsync() -// { -// await base.OnDisposeAsync(); -// // if (_clientServiceContext is not null) -// // { -// // _logger.LogDebug("ClientServiceContext is closing: {EndPoint}", _info.EndPoint); -// // await _clientServiceContext.CloseAsync(_closeToken, CancellationToken.None); -// // _clientServiceContext = null; -// // _logger.LogDebug("ClientServiceContext is closed: {EndPoint}", _info.EndPoint); -// // } - -// await _waitForExitTask; -// } - -// private static async Task WaitForExit(Process process, Action cancelAction) -// { -// await process.WaitForExitAsync(); -// cancelAction.Invoke(); -// } - -// private async Task AutoStartAsync(CancellationToken cancellationToken) -// { -// if (_info.NodeEndPoint is { } nodeEndPoint) -// { -// _client.NodeEndPoint = nodeEndPoint; -// _logger.LogDebug("Client auto-starting: {EndPoint}", nodeEndPoint); -// await _client.StartAsync(cancellationToken); -// _logger.LogDebug("Client auto-started: {EndPoint}", nodeEndPoint); -// } -// } -// } diff --git a/src/client/LibplanetConsole.Client/ApplicationInfoProvider.cs b/src/client/LibplanetConsole.Client/ApplicationInfoProvider.cs index 88b86d0b..bdcf2b57 100644 --- a/src/client/LibplanetConsole.Client/ApplicationInfoProvider.cs +++ b/src/client/LibplanetConsole.Client/ApplicationInfoProvider.cs @@ -14,10 +14,8 @@ public ApplicationInfoProvider(ApplicationOptions options) _info = new() { EndPoint = options.EndPoint, - // SeedEndPoint = options.SeedEndPoint, - // StorePath = options.StorePath, + NodeEndPoint = options.NodeEndPoint, LogPath = options.LogPath, - // ParentProcessId = options.ParentProcessId, }; } diff --git a/src/client/LibplanetConsole.Client/Client.BlockChain.cs b/src/client/LibplanetConsole.Client/Client.BlockChain.cs index 3d4c2fa7..406a00dd 100644 --- a/src/client/LibplanetConsole.Client/Client.BlockChain.cs +++ b/src/client/LibplanetConsole.Client/Client.BlockChain.cs @@ -1,7 +1,7 @@ using System.Security.Cryptography; -using LibplanetConsole.Common; +using Grpc.Core; using LibplanetConsole.Node; -using Microsoft.Extensions.Logging; +using LibplanetConsole.Node.Grpc; namespace LibplanetConsole.Client; @@ -12,37 +12,76 @@ internal sealed partial class Client : IBlockChain public async Task SendTransactionAsync( IAction[] actions, CancellationToken cancellationToken) { - // var privateKey = PrivateKeyUtility.FromSecureString(_privateKey); - // var address = privateKey.Address; - // var nonce = await RemoteBlockChainService.GetNextNonceAsync(address, cancellationToken); - // var genesisHash = NodeInfo.GenesisHash; - // var tx = Transaction.Create( - // nonce: nonce, - // privateKey: privateKey, - // genesisHash: genesisHash, - // actions: [.. actions.Select(item => item.PlainValue)]); - // var txData = tx.Serialize(); - // _logger.LogDebug("Client sends a transaction: {TxId}", tx.Id); - // return await RemoteBlockChainService.SendTransactionAsync(txData, cancellationToken); - throw new NotImplementedException(); + if (_blockChainService is null) + { + throw new InvalidOperationException("BlockChainService is not initialized."); + } + + var address = _privateKey.Address; + var nonce = await GetNextNonceAsync(address, cancellationToken); + var genesisHash = NodeInfo.GenesisHash; + var tx = Transaction.Create( + nonce: nonce, + privateKey: _privateKey, + genesisHash: genesisHash, + actions: [.. actions.Select(item => item.PlainValue)]); + var txData = tx.Serialize(); + var request = new SendTransactionRequest + { + TransactionData = Google.Protobuf.ByteString.CopyFrom(txData), + }; + var callOptions = new CallOptions( + cancellationToken: cancellationToken); + var response = await _blockChainService.SendTransactionAsync(request, callOptions); + return TxId.FromHex(response.TxId); } - public Task GetBlockHashAsync(long height, CancellationToken cancellationToken) + public async Task GetBlockHashAsync(long height, CancellationToken cancellationToken) { - // => RemoteBlockChainService.GetBlockHashAsync(height, cancellationToken); - throw new NotImplementedException(); + if (_blockChainService is null) + { + throw new InvalidOperationException("BlockChainService is not initialized."); + } + + var request = new GetBlockHashRequest + { + Height = height, + }; + var options = new CallOptions( + cancellationToken: cancellationToken); + var response = await _blockChainService.GetBlockHashAsync(request, options); + return BlockHash.FromString(response.BlockHash); } - public Task GetNextNonceAsync(Address address, CancellationToken cancellationToken) + public async Task GetNextNonceAsync(Address address, CancellationToken cancellationToken) { - // return RemoteBlockChainService.GetNextNonceAsync(address, cancellationToken); - throw new NotImplementedException(); + if (_blockChainService is null) + { + throw new InvalidOperationException("BlockChainService is not initialized."); + } + + var request = new GetNextNonceRequest + { + Address = address.ToHex(), + }; + var options = new CallOptions( + cancellationToken: cancellationToken); + var response = await _blockChainService.GetNextNonceAsync(request, options); + return response.Nonce; } - public Task GetTipHashAsync(CancellationToken cancellationToken) + public async Task GetTipHashAsync(CancellationToken cancellationToken) { - // return RemoteBlockChainService.GetTipHashAsync(cancellationToken); - throw new NotImplementedException(); + if (_blockChainService is null) + { + throw new InvalidOperationException("BlockChainService is not initialized."); + } + + var request = new GetTipHashRequest(); + var options = new CallOptions( + cancellationToken: cancellationToken); + var response = await _blockChainService.GetTipHashAsync(request, options); + return BlockHash.FromString(response.BlockHash); } public async Task GetStateAsync( @@ -51,10 +90,21 @@ public async Task GetStateAsync( Address address, CancellationToken cancellationToken) { - // var value = await RemoteBlockChainService.GetStateAsync( - // blockHash, accountAddress, address, cancellationToken); - // return _codec.Decode(value); - throw new NotImplementedException(); + if (_blockChainService is null) + { + throw new InvalidOperationException("BlockChainService is not initialized."); + } + + var request = new GetStateRequest + { + BlockHash = blockHash?.ToString() ?? string.Empty, + AccountAddress = accountAddress.ToHex(), + Address = address.ToHex(), + }; + var options = new CallOptions( + cancellationToken: cancellationToken); + var response = await _blockChainService.GetStateAsync(request, options); + return _codec.Decode(response.StateData.ToByteArray()); } public async Task GetStateByStateRootHashAsync( @@ -63,26 +113,47 @@ public async Task GetStateByStateRootHashAsync( Address address, CancellationToken cancellationToken) { - // var value = await RemoteBlockChainService.GetStateByStateRootHashAsync( - // stateRootHash, accountAddress, address, cancellationToken); - // return _codec.Decode(value); - throw new NotImplementedException(); + if (_blockChainService is null) + { + throw new InvalidOperationException("BlockChainService is not initialized."); + } + + var request = new GetStateRequest + { + StateRootHash = stateRootHash.ToString(), + AccountAddress = accountAddress.ToHex(), + Address = address.ToHex(), + }; + var options = new CallOptions( + cancellationToken: cancellationToken); + var response = await _blockChainService.GetStateAsync(request, options); + return _codec.Decode(response.StateData.ToByteArray()); } public async Task GetActionAsync( TxId txId, int actionIndex, CancellationToken cancellationToken) where T : IAction { - // var bytes = await RemoteBlockChainService.GetActionAsync( - // txId, actionIndex, cancellationToken); - // var value = _codec.Decode(bytes); - // if (Activator.CreateInstance(typeof(T)) is T action) - // { - // action.LoadPlainValue(value); - // return action; - // } - - // throw new InvalidOperationException("Action not found."); - throw new NotImplementedException(); + if (_blockChainService is null) + { + throw new InvalidOperationException("BlockChainService is not initialized."); + } + + var request = new GetActionRequest + { + TxId = txId.ToHex(), + ActionIndex = actionIndex, + }; + var options = new CallOptions( + cancellationToken: cancellationToken); + var response = await _blockChainService.GetActionAsync(request, options); + var value = _codec.Decode(response.ActionData.ToByteArray()); + if (Activator.CreateInstance(typeof(T)) is T action) + { + action.LoadPlainValue(value); + return action; + } + + throw new InvalidOperationException("Action not found."); } } diff --git a/src/client/LibplanetConsole.Client/Client.cs b/src/client/LibplanetConsole.Client/Client.cs index 131255e8..1b9d2360 100644 --- a/src/client/LibplanetConsole.Client/Client.cs +++ b/src/client/LibplanetConsole.Client/Client.cs @@ -1,35 +1,29 @@ -using System.Security; -using Grpc.Core; using Grpc.Net.Client; +using LibplanetConsole.Client.Grpc; using LibplanetConsole.Common; using LibplanetConsole.Common.Extensions; using LibplanetConsole.Node; -using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Logging; namespace LibplanetConsole.Client; internal sealed partial class Client : IClient { - private readonly IServiceProvider _serviceProvider; - private readonly SecureString _privateKey; private readonly ILogger _logger; + private readonly PrivateKey _privateKey; private EndPoint? _nodeEndPoint; - private Node.Grpc.NodeGrpcService.NodeGrpcServiceClient? _remoteNode; - private Node.Grpc.BlockChainGrpcService.BlockChainGrpcServiceClient? _remoteBlockchain; + private NodeService? _nodeService; + private BlockChainService? _blockChainService; private GrpcChannel? _channel; private CancellationTokenSource? _cancellationTokenSource; - private Task _callbackTask = Task.CompletedTask; - private Guid _closeToken; private ClientInfo _info; - public Client(IServiceProvider serviceProvider, ApplicationOptions options) + public Client(ILogger logger, ApplicationOptions options) { - _serviceProvider = serviceProvider; - _logger = serviceProvider.GetLogger(); + _logger = logger; + _privateKey = options.PrivateKey; _logger.LogDebug("Client is creating...: {Address}", options.PrivateKey.Address); _nodeEndPoint = options.NodeEndPoint; - _privateKey = options.PrivateKey.ToSecureString(); _info = new() { Address = options.PrivateKey.Address }; PublicKey = options.PrivateKey.PublicKey; _logger.LogDebug("Client is created: {Address}", Address); @@ -79,26 +73,25 @@ public async Task StartAsync(CancellationToken cancellationToken) throw new InvalidOperationException("The client is already running."); } + await Task.Delay(1000); + var address = $"http://{EndPointUtility.ToString(_nodeEndPoint)}"; var channelOptions = new GrpcChannelOptions { ThrowOperationCanceledOnCancellation = true, MaxRetryAttempts = 1, }; - HubConnectionContext _channel = GrpcChannel.ForAddress(address, channelOptions); _cancellationTokenSource = new(); - _remoteNode = new Node.Grpc.NodeGrpcService.NodeGrpcServiceClient(_channel); - _remoteBlockchain = new Node.Grpc.BlockChainGrpcService.BlockChainGrpcServiceClient(_channel); - _callbackTask = CallbackAsync(_cancellationTokenSource.Token); - - _channel.Target - - // _remoteNodeContext = _serviceProvider.GetRequiredService(); - // _remoteNodeContext.EndPoint = NodeEndPoint; - // _closeToken = await _remoteNodeContext.OpenAsync(cancellationToken); - // _remoteNodeContext.Closed += RemoteNodeContext_Closed; - // NodeInfo = await RemoteNodeService.GetInfoAsync(cancellationToken); + _nodeService = new NodeService(_channel); + _nodeService.Disconnected += NodeService_Disconnected; + _nodeService.Started += (sender, e) => InvokeNodeStartedEvent(e); + _nodeService.Stopped += (sender, e) => InvokeNodeStoppedEvent(); + _blockChainService = new BlockChainService(_channel); + _blockChainService.BlockAppended += (sender, e) => InvokeBlockAppendedEvent(e); + await Task.WhenAll( + _nodeService.StartAsync(cancellationToken), + _blockChainService.StartAsync(cancellationToken)); _info = _info with { NodeAddress = NodeInfo.Address }; IsRunning = true; _logger.LogDebug( @@ -106,51 +99,39 @@ public async Task StartAsync(CancellationToken cancellationToken) Started?.Invoke(this, EventArgs.Empty); } - private async Task CallbackAsync(CancellationToken cancellationToken) + public async Task StopAsync(CancellationToken cancellationToken) { - try + if (_channel is null) { - await Task.Delay(2000, cancellationToken); - var callOptions = new CallOptions(deadline: DateTime.UtcNow.AddSeconds(1), cancellationToken: cancellationToken); - var response1 = await _remoteBlockchain.IsReadyAsync(new Node.Grpc.IsReadyRequest(), callOptions); - if (response1.IsReady is false) - { - throw new InvalidOperationException("The remote node is not ready."); - } - - using var streamingCall = _remoteBlockchain.GetBlockAppendedStream( - new Node.Grpc.GetBlockAppendedStreamRequest(), - deadline: DateTime.UtcNow + TimeSpan.FromSeconds(10), - cancellationToken: cancellationToken); + throw new InvalidOperationException("The client is not running."); + } - while (await streamingCall.ResponseStream.MoveNext(cancellationToken)) - { - var response = streamingCall.ResponseStream.Current; - InvokeBlockAppendedEvent(response.BlockInfo); - } + if (_cancellationTokenSource is not null) + { + await _cancellationTokenSource.CancelAsync(); + _cancellationTokenSource.Dispose(); } - catch (Exception e) + + if (_nodeService is not null) { - int qwer = 0; + _nodeService.Disconnected -= NodeService_Disconnected; + await _nodeService.StopAsync(cancellationToken); + _nodeService = null; } - } - public async Task StopAsync(CancellationToken cancellationToken) - { - if (_channel is null) + if (_blockChainService is not null) { - throw new InvalidOperationException("The client is not running."); + await _blockChainService.StopAsync(cancellationToken); + _blockChainService = null; } - // _remoteNodeContext.Closed -= RemoteNodeContext_Closed; - // await _remoteNodeContext.CloseAsync(_closeToken, cancellationToken); - // _info = _info with { NodeAddress = default }; - // _remoteNodeContext = null; await _channel.ShutdownAsync(); _channel.Dispose(); _channel = null; - _closeToken = Guid.Empty; + _blockChainService = null; + _nodeService = null; IsRunning = false; + _info = _info with { NodeAddress = default }; _logger.LogDebug("Client is stopped: {Address}", Address); Stopped?.Invoke(this, new(StopReason.None)); } @@ -172,33 +153,28 @@ public void InvokeBlockAppendedEvent(BlockInfo blockInfo) public async ValueTask DisposeAsync() { - // if (_remoteNodeContext is not null) - // { - // _remoteNodeContext.Closed -= RemoteNodeContext_Closed; - // await _remoteNodeContext.CloseAsync(_closeToken); - // _remoteNodeContext = null; - // } + _nodeService?.Dispose(); + _nodeService = null; + _blockChainService?.Dispose(); + _blockChainService = null; + _channel?.Dispose(); + _channel = null; + IsRunning = false; + await ValueTask.CompletedTask; } - // void INodeCallback.OnStarted(NodeInfo nodeInfo) => NodeInfo = nodeInfo; - - // void INodeCallback.OnStopped() => NodeInfo = NodeInfo.Empty; - - // void IBlockChainCallback.OnBlockAppended(BlockInfo blockInfo) - // { - // BlockAppended?.Invoke(this, new BlockEventArgs(blockInfo)); - // } - - private void RemoteNodeContext_Closed(object? sender, EventArgs e) + private void NodeService_Disconnected(object? sender, EventArgs e) { - // if (_remoteNodeContext is not null) - // { - // _remoteNodeContext.Closed -= RemoteNodeContext_Closed; - // _remoteNodeContext = null; - // } - - _closeToken = Guid.Empty; - IsRunning = false; - Stopped?.Invoke(this, new(StopReason.None)); + if (_cancellationTokenSource?.IsCancellationRequested is false) + { + _nodeService?.Dispose(); + _nodeService = null; + _blockChainService?.Dispose(); + _blockChainService = null; + _channel?.Dispose(); + _channel = null; + IsRunning = false; + Stopped?.Invoke(this, new(StopReason.None)); + } } } diff --git a/src/client/LibplanetConsole.Client/ClientHostedService.cs b/src/client/LibplanetConsole.Client/ClientHostedService.cs index c06361fa..652cd526 100644 --- a/src/client/LibplanetConsole.Client/ClientHostedService.cs +++ b/src/client/LibplanetConsole.Client/ClientHostedService.cs @@ -15,10 +15,9 @@ public Task StartAsync(CancellationToken cancellationToken) applicationLifetime.ApplicationStarted.Register(async () => { logger.LogInformation("Application started."); - if (options.NodeEndPoint is { } nodeEndPoint) + if (options.NodeEndPoint is not null) { logger.LogDebug("Client auto-starting"); - // client.SeedEndPoint = nodeEndPoint; await client.StartAsync(cancellationToken); logger.LogDebug("Client auto-started"); } diff --git a/src/client/LibplanetConsole.Client/Grpc/BlockChainService.cs b/src/client/LibplanetConsole.Client/Grpc/BlockChainService.cs new file mode 100644 index 00000000..7ee48c15 --- /dev/null +++ b/src/client/LibplanetConsole.Client/Grpc/BlockChainService.cs @@ -0,0 +1,47 @@ +using Grpc.Net.Client; +using LibplanetConsole.Node; + +namespace LibplanetConsole.Client.Grpc; + +internal sealed class BlockChainService(GrpcChannel channel) + : Node.Grpc.BlockChainGrpcService.BlockChainGrpcServiceClient(channel), IDisposable +{ + private StreamReceiver? _blockAppendedReceiver; + private bool _isDisposed; + + public event EventHandler? BlockAppended; + + public void Dispose() + { + if (_isDisposed is false) + { + _blockAppendedReceiver?.Dispose(); + _blockAppendedReceiver = null; + _isDisposed = true; + } + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + if (_blockAppendedReceiver is not null) + { + throw new InvalidOperationException($"{nameof(BlockChainService)} is already started."); + } + + _blockAppendedReceiver = new( + GetBlockAppendedStream(new(), cancellationToken: cancellationToken), + (response) => BlockAppended?.Invoke(this, response.BlockInfo)); + await _blockAppendedReceiver.StartAsync(cancellationToken); + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + if (_blockAppendedReceiver is null) + { + throw new InvalidOperationException($"{nameof(BlockChainService)} is not started."); + } + + await _blockAppendedReceiver.StopAsync(cancellationToken); + _blockAppendedReceiver = null; + } +} diff --git a/src/client/LibplanetConsole.Client/Grpc/Connection.cs b/src/client/LibplanetConsole.Client/Grpc/Connection.cs new file mode 100644 index 00000000..33eb37e1 --- /dev/null +++ b/src/client/LibplanetConsole.Client/Grpc/Connection.cs @@ -0,0 +1,10 @@ +namespace LibplanetConsole.Client.Grpc; + +internal sealed class Connection(NodeService nodeService) + : ConnectionMonitor(nodeService, FuncAsync) +{ + private static async Task FuncAsync(NodeService client, CancellationToken cancellationToken) + { + await client.PingAsync(new(), cancellationToken: cancellationToken); + } +} diff --git a/src/client/LibplanetConsole.Client/Grpc/NodeService.cs b/src/client/LibplanetConsole.Client/Grpc/NodeService.cs new file mode 100644 index 00000000..fe2234e5 --- /dev/null +++ b/src/client/LibplanetConsole.Client/Grpc/NodeService.cs @@ -0,0 +1,92 @@ +using Grpc.Net.Client; +using LibplanetConsole.Node; + +namespace LibplanetConsole.Client.Grpc; + +internal sealed class NodeService(GrpcChannel channel) + : Node.Grpc.NodeGrpcService.NodeGrpcServiceClient(channel), IDisposable +{ + private Connection? _connection; + private StreamReceiver? _startedReceiver; + private StreamReceiver? _stoppedReceiver; + private bool _isDisposed; + + public event EventHandler? Disconnected; + + public event EventHandler? Started; + + public event EventHandler? Stopped; + + public void Dispose() + { + if (_isDisposed is false) + { + _startedReceiver?.Dispose(); + _startedReceiver = null; + _stoppedReceiver?.Dispose(); + _stoppedReceiver = null; + _connection?.Dispose(); + _connection = null; + _isDisposed = true; + } + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + if (_connection is not null) + { + throw new InvalidOperationException($"{nameof(NodeService)} is already started."); + } + + _connection = new Connection(this); + _connection.Disconnected += Connection_Disconnected; + await _connection.StartAsync(cancellationToken); + _startedReceiver = new( + GetStartedStream(new(), cancellationToken: cancellationToken), + (response) => Started?.Invoke(this, response.NodeInfo)); + _stoppedReceiver = new( + GetStoppedStream(new(), cancellationToken: cancellationToken), + (response) => Stopped?.Invoke(this, EventArgs.Empty)); + await Task.WhenAll( + _startedReceiver.StartAsync(cancellationToken), + _stoppedReceiver.StartAsync(cancellationToken)); + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + if (_connection is null) + { + throw new InvalidOperationException($"{nameof(NodeService)} is not started."); + } + + if (_startedReceiver is not null) + { + await _startedReceiver.StopAsync(cancellationToken); + _startedReceiver = null; + } + + if (_stoppedReceiver is not null) + { + await _stoppedReceiver.StopAsync(cancellationToken); + _stoppedReceiver = null; + } + + _connection.Disconnected -= Connection_Disconnected; + await _connection.StopAsync(cancellationToken); + _connection = null; + } + + private void Connection_Disconnected(object? sender, EventArgs e) + { + if (sender is Connection connection && connection == _connection) + { + _startedReceiver?.Dispose(); + _startedReceiver = null; + _stoppedReceiver?.Dispose(); + _stoppedReceiver = null; + _connection.Dispose(); + _connection = null; + Disconnected?.Invoke(this, e); + } + } +} diff --git a/src/client/LibplanetConsole.Client/IApplication.cs b/src/client/LibplanetConsole.Client/IApplication.cs deleted file mode 100644 index 8f184db8..00000000 --- a/src/client/LibplanetConsole.Client/IApplication.cs +++ /dev/null @@ -1,12 +0,0 @@ -// namespace LibplanetConsole.Client; - -// public interface IApplication : IAsyncDisposable -// { -// ApplicationInfo Info { get; } - -// Task InvokeAsync(Action action, CancellationToken cancellationToken); - -// Task InvokeAsync(Func func, CancellationToken cancellationToken); - -// void Cancel(); -// } diff --git a/src/client/LibplanetConsole.Client/LibplanetConsole.Client.csproj b/src/client/LibplanetConsole.Client/LibplanetConsole.Client.csproj index db74743d..dcbf870c 100644 --- a/src/client/LibplanetConsole.Client/LibplanetConsole.Client.csproj +++ b/src/client/LibplanetConsole.Client/LibplanetConsole.Client.csproj @@ -7,6 +7,7 @@ + diff --git a/src/client/LibplanetConsole.Client/ServiceCollectionExtensions.cs b/src/client/LibplanetConsole.Client/ServiceCollectionExtensions.cs index 5f7a2b71..fe2f04f4 100644 --- a/src/client/LibplanetConsole.Client/ServiceCollectionExtensions.cs +++ b/src/client/LibplanetConsole.Client/ServiceCollectionExtensions.cs @@ -1,6 +1,6 @@ +using JSSoft.Commands; using LibplanetConsole.Client.Commands; using LibplanetConsole.Common; -// using LibplanetConsole.Common.Services; using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Client; @@ -15,29 +15,20 @@ public static IServiceCollection AddClient( @this.AddSingleton(synchronizationContext); @this.AddSingleton(options); - @this.AddSingleton(s => new Client(s, options)) + @this.AddSingleton() .AddSingleton(s => s.GetRequiredService()) .AddSingleton(s => s.GetRequiredService()); @this.AddHostedService(); - // @this.AddSingleton() - // .AddSingleton(s => s.GetRequiredService()) - // .AddSingleton(s => s.GetRequiredService()); - // @this.AddSingleton(); - // @this.AddSingleton(); - // .AddSingleton(s => s.GetRequiredService()); - // @this.AddSingleton(); - // @this.AddSingleton(); - // .AddSingleton(s => s.GetRequiredService()); @this.AddSingleton(); @this.AddSingleton(); - @this.AddSingleton(); - @this.AddSingleton(); - @this.AddSingleton(); - @this.AddSingleton(); - @this.AddSingleton(); - @this.AddSingleton(); - @this.AddSingleton(); + @this.AddSingleton(); + @this.AddSingleton(); + @this.AddSingleton(); + @this.AddSingleton(); + @this.AddSingleton(); + @this.AddSingleton(); + @this.AddSingleton(); return @this; } } diff --git a/src/common/LibplanetConsole.Settings/LibplanetConsole.Settings.csproj b/src/common/LibplanetConsole.Settings/LibplanetConsole.Settings.csproj index 87e09ecb..e16d8b14 100644 --- a/src/common/LibplanetConsole.Settings/LibplanetConsole.Settings.csproj +++ b/src/common/LibplanetConsole.Settings/LibplanetConsole.Settings.csproj @@ -1,12 +1,16 @@ - - - - - - - + + + + + all + + + all + + + - - - + + + diff --git a/src/node/LibplanetConsole.Node/LibplanetConsole.Node.csproj b/src/node/LibplanetConsole.Node/LibplanetConsole.Node.csproj index 18cb001d..86f038fb 100644 --- a/src/node/LibplanetConsole.Node/LibplanetConsole.Node.csproj +++ b/src/node/LibplanetConsole.Node/LibplanetConsole.Node.csproj @@ -23,6 +23,7 @@ + diff --git a/src/node/LibplanetConsole.Node/Services/NodeGrpcServiceV1.cs b/src/node/LibplanetConsole.Node/Services/NodeGrpcServiceV1.cs index c400ca5a..031d217c 100644 --- a/src/node/LibplanetConsole.Node/Services/NodeGrpcServiceV1.cs +++ b/src/node/LibplanetConsole.Node/Services/NodeGrpcServiceV1.cs @@ -8,6 +8,11 @@ internal sealed class NodeGrpcServiceV1( IHostApplicationLifetime applicationLifetime, INode node) : NodeGrpcService.NodeGrpcServiceBase { + public override Task Ping(PingRequest request, ServerCallContext context) + { + return Task.FromResult(new PingResponse()); + } + public override async Task Start(StartRequest request, ServerCallContext context) { await node.StartAsync(context.CancellationToken); diff --git a/src/shared/LibplanetConsole.Grpc/ConnectionMonitor.cs b/src/shared/LibplanetConsole.Grpc/ConnectionMonitor.cs new file mode 100644 index 00000000..c092b97e --- /dev/null +++ b/src/shared/LibplanetConsole.Grpc/ConnectionMonitor.cs @@ -0,0 +1,36 @@ +using Grpc.Core; +using LibplanetConsole.Common.Threading; + +#if LIBPLANET_CONSOLE +namespace LibplanetConsole.Console.Grpc; +#elif LIBPLANET_NODE +namespace LibplanetConsole.Node.Grpc; +#elif LIBPLANET_CLIENT +namespace LibplanetConsole.Client.Grpc; +#else +#error "Either LIBPLANET_CONSOLE or LIBPLANET_NODE must be defined." +#endif + +internal class ConnectionMonitor(T client, Func action) + : RunTask +{ + public event EventHandler? Disconnected; + + public TimeSpan Interval { get; set; } = TimeSpan.FromSeconds(5); + + protected override async Task OnRunAsync(CancellationToken cancellationToken) + { + while (await TaskUtility.TryDelay(1, cancellationToken)) + { + try + { + await action(client, cancellationToken); + } + catch (RpcException) + { + Disconnected?.Invoke(this, EventArgs.Empty); + break; + } + } + } +} diff --git a/src/node/LibplanetConsole.Node/Services/EventStreamer.cs b/src/shared/LibplanetConsole.Grpc/EventStreamer.cs similarity index 87% rename from src/node/LibplanetConsole.Node/Services/EventStreamer.cs rename to src/shared/LibplanetConsole.Grpc/EventStreamer.cs index 697b1a0c..578fbcce 100644 --- a/src/node/LibplanetConsole.Node/Services/EventStreamer.cs +++ b/src/shared/LibplanetConsole.Grpc/EventStreamer.cs @@ -1,7 +1,15 @@ #pragma warning disable SA1402 // File may only contain a single type using Grpc.Core; -namespace LibplanetConsole.Node.Services; +#if LIBPLANET_CONSOLE +namespace LibplanetConsole.Console.Grpc; +#elif LIBPLANET_NODE +namespace LibplanetConsole.Node.Grpc; +#elif LIBPLANET_CLIENT +namespace LibplanetConsole.Client.Grpc; +#else +#error "Either LIBPLANET_CONSOLE or LIBPLANET_NODE must be defined." +#endif internal sealed class EventStreamer( IAsyncStreamWriter streamWriter, diff --git a/src/shared/LibplanetConsole.Grpc/RunTask.cs b/src/shared/LibplanetConsole.Grpc/RunTask.cs new file mode 100644 index 00000000..520c386b --- /dev/null +++ b/src/shared/LibplanetConsole.Grpc/RunTask.cs @@ -0,0 +1,83 @@ +#if LIBPLANET_CONSOLE +namespace LibplanetConsole.Console.Grpc; +#elif LIBPLANET_NODE +namespace LibplanetConsole.Node.Grpc; +#elif LIBPLANET_CLIENT +namespace LibplanetConsole.Client.Grpc; +#else +#error "Either LIBPLANET_CONSOLE or LIBPLANET_NODE must be defined." +#endif + +internal abstract class RunTask : IDisposable +{ + private bool _isRunning; + private bool _disposedValue; + private CancellationTokenSource? _cancellationTokenSource; + private Task _runningTask = Task.CompletedTask; + + public bool IsRunning => _isRunning; + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + if (_isRunning is true) + { + throw new InvalidOperationException( + $"{GetType().Name} is already running."); + } + + _cancellationTokenSource = new(); + _runningTask = OnRunAsync(_cancellationTokenSource.Token); + _isRunning = true; + await Task.CompletedTask; + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + if (_isRunning is false) + { + throw new InvalidOperationException( + $"{GetType().Name} is not running."); + } + + if (_cancellationTokenSource is not null) + { + await _cancellationTokenSource.CancelAsync(); + _cancellationTokenSource.Dispose(); + _cancellationTokenSource = null; + } + + await _runningTask; + _isRunning = false; + } + + public async Task RunAsync(CancellationToken cancellationToken) + { + using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource( + cancellationToken); + await StartAsync(cancellationTokenSource.Token); + await _runningTask; + await StopAsync(cancellationTokenSource.Token); + } + + protected abstract Task OnRunAsync(CancellationToken cancellationToken); + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = null; + } + + _disposedValue = true; + } + } +} diff --git a/src/shared/LibplanetConsole.Grpc/StreamReceiver.cs b/src/shared/LibplanetConsole.Grpc/StreamReceiver.cs new file mode 100644 index 00000000..15900669 --- /dev/null +++ b/src/shared/LibplanetConsole.Grpc/StreamReceiver.cs @@ -0,0 +1,37 @@ +#pragma warning disable SA1402 // File may only contain a single type +using Grpc.Core; + +#if LIBPLANET_CONSOLE +namespace LibplanetConsole.Console.Grpc; +#elif LIBPLANET_NODE +namespace LibplanetConsole.Node.Grpc; +#elif LIBPLANET_CLIENT +namespace LibplanetConsole.Client.Grpc; +#else +#error "Either LIBPLANET_CONSOLE or LIBPLANET_NODE must be defined." +#endif + +internal sealed class StreamReceiver( + AsyncServerStreamingCall streamingCall, + Action action) : RunTask +{ + protected override async Task OnRunAsync(CancellationToken cancellationToken) + { + try + { + while (await streamingCall.ResponseStream.MoveNext(cancellationToken)) + { + var response = streamingCall.ResponseStream.Current; + action.Invoke(response); + } + } + catch (RpcException ex) when (ex.StatusCode == StatusCode.Cancelled) + { + // Ignore + } + catch (OperationCanceledException) + { + // Ignore + } + } +} diff --git a/src/node/LibplanetConsole.Node/Services/Streamer.cs b/src/shared/LibplanetConsole.Grpc/Streamer.cs similarity index 86% rename from src/node/LibplanetConsole.Node/Services/Streamer.cs rename to src/shared/LibplanetConsole.Grpc/Streamer.cs index 439680ab..8fec405a 100644 --- a/src/node/LibplanetConsole.Node/Services/Streamer.cs +++ b/src/shared/LibplanetConsole.Grpc/Streamer.cs @@ -1,7 +1,15 @@ using Grpc.Core; using Microsoft.Extensions.Hosting; -namespace LibplanetConsole.Node.Services; +#if LIBPLANET_CONSOLE +namespace LibplanetConsole.Console.Grpc; +#elif LIBPLANET_NODE +namespace LibplanetConsole.Node.Grpc; +#elif LIBPLANET_CLIENT +namespace LibplanetConsole.Client.Grpc; +#else +#error "Either LIBPLANET_CONSOLE or LIBPLANET_NODE must be defined." +#endif internal abstract class Streamer(IAsyncStreamWriter streamWriter) { diff --git a/src/shared/LibplanetConsole.Node/Protos/BlockChainGrpcService.proto b/src/shared/LibplanetConsole.Node/Protos/BlockChainGrpcService.proto index 5f4962a2..d0453f9f 100644 --- a/src/shared/LibplanetConsole.Node/Protos/BlockChainGrpcService.proto +++ b/src/shared/LibplanetConsole.Node/Protos/BlockChainGrpcService.proto @@ -26,15 +26,15 @@ message IsReadyRequest { } message IsReadyResponse { - bool isReady = 1; + bool is_ready = 1; } message SendTransactionRequest { - bytes transactionData = 1; + bytes transaction_data = 1; } message SendTransactionResponse { - string txId = 1; + string tx_id = 1; } message GetNextNonceRequest { @@ -49,17 +49,20 @@ message GetTipHashRequest { } message GetTipHashResponse { - string blockHash = 1; + string block_hash = 1; } message GetStateRequest { - optional string blockHash = 1; - string accountAddress = 2; - string address = 3; + oneof hash { + string block_hash = 1; + string state_root_hash = 2; + } + string account_address = 3; + string address = 4; } message GetStateResponse { - bytes stateData = 1; + bytes state_data = 1; } message GetBlockHashRequest { @@ -67,21 +70,21 @@ message GetBlockHashRequest { } message GetBlockHashResponse { - string blockHash = 1; + string block_hash = 1; } message GetActionRequest { - string txId = 1; - int32 actionIndex = 2; + string tx_id = 1; + int32 action_index = 2; } message GetActionResponse { - bytes actionData = 1; + bytes action_data = 1; } message GetBlockAppendedStreamRequest { } message GetBlockAppendedStreamResponse { - BlockInfo blockInfo = 1; + BlockInfo block_info = 1; } diff --git a/src/shared/LibplanetConsole.Node/Protos/NodeGrpcService.proto b/src/shared/LibplanetConsole.Node/Protos/NodeGrpcService.proto index 69f6ddf1..7299f3c4 100644 --- a/src/shared/LibplanetConsole.Node/Protos/NodeGrpcService.proto +++ b/src/shared/LibplanetConsole.Node/Protos/NodeGrpcService.proto @@ -5,6 +5,7 @@ option csharp_namespace = "LibplanetConsole.Node.Grpc"; package libplanet.console.node.v1; service NodeGrpcService { + rpc Ping(PingRequest) returns (PingResponse); rpc Start(StartRequest) returns (StartResponse); rpc Stop(StopRequest) returns (StopResponse); rpc GetInfo(GetInfoRequest) returns (GetInfoResponse); @@ -24,6 +25,12 @@ message NodeInfo { bool is_running = 8; } +message PingRequest { +} + +message PingResponse { +} + message StartRequest { string seed_end_point = 1; } From 0ef3b8f0538de32a6070a96cc97096d01564782e Mon Sep 17 00:00:00 2001 From: s2quake Date: Fri, 11 Oct 2024 09:59:56 +0900 Subject: [PATCH 09/18] refactor: Successful block mining --- Directory.Build.props | 1 + .../Application.cs | 64 ---- .../EntryCommands/RunCommand.cs | 1 - .../EntryCommands/StartCommand.cs | 11 +- .../ServiceCollectionExtensions.cs | 6 +- .../TerminalHostedService.cs | 23 +- .../Tracers/BlockChainEventTracer.cs | 2 +- .../Client.BlockChain.cs | 39 ++- src/client/LibplanetConsole.Client/Client.cs | 59 ++-- .../ClientHostedService.cs | 1 - .../Grpc}/BlockChainGrpcServiceV1.cs | 21 +- .../Grpc/ClientGrpcServiceV1.cs} | 30 +- .../Grpc/Connection.cs | 10 - .../Grpc/NodeChannel.cs | 49 +++ .../LibplanetConsole.Client/IBlockChain.cs | 1 + src/client/LibplanetConsole.Client/IClient.cs | 2 +- .../LibplanetConsole.Client.csproj | 12 +- .../NodeEndpointRouteBuilderExtensions.cs | 16 + .../InvalidOperationExceptionUtility.cs | 12 - .../Services/ILocalService.cs | 8 - .../Services/IRemoteService.cs | 8 - .../Services/LocalService.cs | 62 ---- .../Services/LocalServiceContext.cs | 69 ----- .../Services/RemoteService.cs | 50 ---- .../Services/RemoteServiceContext.cs | 64 ---- .../LibplanetConsole.Common/StopEventArgs.cs | 6 - .../LibplanetConsole.Common/StopReason.cs | 19 -- .../Threading/TaskUtility.cs | 14 + .../ApplicationFramework.cs | 107 ------- .../ApplicationServiceCollection.cs | 15 +- .../ApplicationSettingsCollection.cs | 14 +- .../IApplicationService.cs | 6 - .../EntryCommands/InitializeCommand.cs | 10 + .../EntryCommands/RunCommand.cs | 37 ++- .../EntryCommands/StartCommand.cs | 72 +++-- ...lication.cs => ExecutableHostedService.cs} | 31 +- ...LibplanetConsole.Console.Executable.csproj | 2 +- .../NodeGenesisProcess.cs | 1 + .../Repository.cs | 1 + .../ServiceCollectionExtensions.cs | 12 +- .../SystemTerminal.cs | 6 +- .../Tracers/ClientCollectionEventTracer.cs | 18 +- .../Tracers/NodeCollectionEventTracer.cs | 24 +- .../ApplicationBase.cs | 123 -------- .../ApplicationInfoProvider.cs | 17 +- .../Client.BlockChain.cs | 161 ++++++++++ .../LibplanetConsole.Console/Client.cs | 280 ++++++++++-------- .../ClientCollection.cs | 79 +++-- .../Commands/ClientCommand.cs | 33 ++- .../Commands/ExitCommand.cs | 18 +- .../Commands/InfoCommand.cs | 7 +- .../Commands/NodeCommand.cs | 25 +- .../Commands/TxCommand.cs | 23 +- .../ConsoleHostedService.cs | 57 ++++ .../ConsoleServiceContext.cs | 8 - .../Extensions/IClientCollectionExtensions.cs | 14 + .../Extensions/INodeCollectionExtensions.cs | 14 + .../Grpc/ClientChannel.cs | 49 +++ .../Grpc/NodeChannel.cs | 49 +++ .../Grpc}/SeedGrpcServiceV1.cs | 2 +- .../LibplanetConsole.Console/IApplication.cs | 21 -- .../LibplanetConsole.Console/IBlockChain.cs | 1 + .../LibplanetConsole.Console/IClient.cs | 2 - .../LibplanetConsole.Console.csproj | 12 +- .../Node.BlockChain.cs | 179 +++++++---- src/console/LibplanetConsole.Console/Node.cs | 279 +++++++++-------- .../NodeCollection.cs | 89 ++++-- .../NodeEndpointRouteBuilderExtensions.cs | 15 + .../LibplanetConsole.Console/NodeProcess.cs | 4 +- .../LibplanetConsole.Console/SeedService.cs | 62 ++++ .../ServiceCollectionExtensions.cs | 22 +- .../Services/IClientContentService.cs | 8 - .../Services/INodeContentService.cs | 8 - .../Services/SeedService.cs | 64 ---- .../Services/EvidenceServiceGrpcV1.cs | 1 - .../Application.cs | 73 ----- .../EntryCommands/RunCommand.cs | 1 - .../EntryCommands/StartCommand.cs | 6 +- .../ServiceCollectionExtensions.cs | 2 - .../TerminalHostedService.cs | 25 +- .../Tracers/BlockChainEventTracer.cs | 1 + .../LibplanetConsole.Node/ApplicationBase.cs | 83 ------ .../ApplicationInfoProvider.cs | 3 +- .../BlockChainUtility.cs | 1 + .../LibplanetConsole.Node/BlockUtility.cs | 1 - .../Commands/TxCommand.cs | 2 +- .../Grpc/BlockChainGrpcServiceV1.cs | 97 ++++++ .../Grpc/NodeGrpcServiceV1.cs | 78 +++++ .../Grpc/SeedGrpcServiceV1.cs | 20 ++ src/node/LibplanetConsole.Node/IBlockChain.cs | 3 +- src/node/LibplanetConsole.Node/INode.cs | 2 +- .../LibplanetConsole.Node.csproj | 2 + .../LibplanetConsole.Node/Node.BlockChain.cs | 55 ++-- src/node/LibplanetConsole.Node/Node.cs | 24 +- .../NodeEndpointRouteBuilderExtensions.cs | 2 +- .../NodeHostedService.cs | 47 ++- src/node/LibplanetConsole.Node/SeedService.cs | 32 +- .../ServiceCollectionExtensions.cs | 3 +- .../BlockEventArgs.cs | 6 + .../BlockInfo.Node.cs | 8 +- .../BlockInfo.cs | 10 +- .../Grpc/BlockChainService.cs | 15 +- .../Protos/BlockChainGrpcService.proto | 16 +- .../ClientEventArgs.cs | 6 + .../LibplanetConsole.Client/ClientInfo.cs | 32 ++ .../Grpc/ClientService.cs | 103 +++++++ .../Protos/ClientGrpcService.proto | 63 ++++ .../Services/IClientCallback.cs | 8 - .../Services/IClientService.cs | 13 - .../Services/TransactionOptions.cs | 8 - .../ConnectionMonitor.cs | 21 +- .../LibplanetConsole.Grpc/EventStreamer.cs | 10 +- src/shared/LibplanetConsole.Grpc/RunTask.cs | 23 +- .../LibplanetConsole.Grpc/StreamReceiver.cs | 10 +- src/shared/LibplanetConsole.Grpc/Streamer.cs | 10 +- .../LoggingExtensions.cs | 2 - .../LibplanetConsole.Node/BlockEventArgs.cs | 16 - .../LibplanetConsole.Node/GenesisOptions.cs | 2 +- .../Grpc/NodeService.cs | 30 +- .../LibplanetConsole.Node/NodeEventArgs.cs | 6 + src/shared/LibplanetConsole.Node/NodeInfo.cs | 22 +- .../Protos/NodeGrpcService.proto | 8 +- 122 files changed, 2060 insertions(+), 1693 deletions(-) delete mode 100644 src/client/LibplanetConsole.Client.Executable/Application.cs rename src/{node/LibplanetConsole.Node/Services => client/LibplanetConsole.Client/Grpc}/BlockChainGrpcServiceV1.cs (83%) rename src/{node/LibplanetConsole.Node/Services/NodeGrpcServiceV1.cs => client/LibplanetConsole.Client/Grpc/ClientGrpcServiceV1.cs} (64%) delete mode 100644 src/client/LibplanetConsole.Client/Grpc/Connection.cs create mode 100644 src/client/LibplanetConsole.Client/Grpc/NodeChannel.cs create mode 100644 src/client/LibplanetConsole.Client/NodeEndpointRouteBuilderExtensions.cs delete mode 100644 src/common/LibplanetConsole.Common/Exceptions/InvalidOperationExceptionUtility.cs delete mode 100644 src/common/LibplanetConsole.Common/Services/ILocalService.cs delete mode 100644 src/common/LibplanetConsole.Common/Services/IRemoteService.cs delete mode 100644 src/common/LibplanetConsole.Common/Services/LocalService.cs delete mode 100644 src/common/LibplanetConsole.Common/Services/LocalServiceContext.cs delete mode 100644 src/common/LibplanetConsole.Common/Services/RemoteService.cs delete mode 100644 src/common/LibplanetConsole.Common/Services/RemoteServiceContext.cs delete mode 100644 src/common/LibplanetConsole.Common/StopEventArgs.cs delete mode 100644 src/common/LibplanetConsole.Common/StopReason.cs delete mode 100644 src/common/LibplanetConsole.Framework/ApplicationFramework.cs delete mode 100644 src/common/LibplanetConsole.Framework/IApplicationService.cs rename src/console/LibplanetConsole.Console.Executable/{Application.cs => ExecutableHostedService.cs} (52%) delete mode 100644 src/console/LibplanetConsole.Console/ApplicationBase.cs create mode 100644 src/console/LibplanetConsole.Console/Client.BlockChain.cs create mode 100644 src/console/LibplanetConsole.Console/ConsoleHostedService.cs delete mode 100644 src/console/LibplanetConsole.Console/ConsoleServiceContext.cs create mode 100644 src/console/LibplanetConsole.Console/Extensions/IClientCollectionExtensions.cs create mode 100644 src/console/LibplanetConsole.Console/Extensions/INodeCollectionExtensions.cs create mode 100644 src/console/LibplanetConsole.Console/Grpc/ClientChannel.cs create mode 100644 src/console/LibplanetConsole.Console/Grpc/NodeChannel.cs rename src/{node/LibplanetConsole.Node/Services => console/LibplanetConsole.Console/Grpc}/SeedGrpcServiceV1.cs (92%) delete mode 100644 src/console/LibplanetConsole.Console/IApplication.cs create mode 100644 src/console/LibplanetConsole.Console/NodeEndpointRouteBuilderExtensions.cs create mode 100644 src/console/LibplanetConsole.Console/SeedService.cs delete mode 100644 src/console/LibplanetConsole.Console/Services/IClientContentService.cs delete mode 100644 src/console/LibplanetConsole.Console/Services/INodeContentService.cs delete mode 100644 src/console/LibplanetConsole.Console/Services/SeedService.cs delete mode 100644 src/node/LibplanetConsole.Node.Executable/Application.cs delete mode 100644 src/node/LibplanetConsole.Node/ApplicationBase.cs create mode 100644 src/node/LibplanetConsole.Node/Grpc/BlockChainGrpcServiceV1.cs create mode 100644 src/node/LibplanetConsole.Node/Grpc/NodeGrpcServiceV1.cs create mode 100644 src/node/LibplanetConsole.Node/Grpc/SeedGrpcServiceV1.cs create mode 100644 src/shared/LibplanetConsole.Blockchain/BlockEventArgs.cs rename src/shared/{LibplanetConsole.Node => LibplanetConsole.Blockchain}/BlockInfo.Node.cs (55%) rename src/shared/{LibplanetConsole.Node => LibplanetConsole.Blockchain}/BlockInfo.cs (68%) rename src/{client/LibplanetConsole.Client => shared/LibplanetConsole.Blockchain}/Grpc/BlockChainService.cs (67%) rename src/shared/{LibplanetConsole.Node => LibplanetConsole.Blockchain}/Protos/BlockChainGrpcService.proto (84%) create mode 100644 src/shared/LibplanetConsole.Client/ClientEventArgs.cs create mode 100644 src/shared/LibplanetConsole.Client/Grpc/ClientService.cs create mode 100644 src/shared/LibplanetConsole.Client/Protos/ClientGrpcService.proto delete mode 100644 src/shared/LibplanetConsole.Client/Services/IClientCallback.cs delete mode 100644 src/shared/LibplanetConsole.Client/Services/IClientService.cs delete mode 100644 src/shared/LibplanetConsole.Client/Services/TransactionOptions.cs delete mode 100644 src/shared/LibplanetConsole.Node/BlockEventArgs.cs rename src/{client/LibplanetConsole.Client => shared/LibplanetConsole.Node}/Grpc/NodeService.cs (70%) create mode 100644 src/shared/LibplanetConsole.Node/NodeEventArgs.cs diff --git a/Directory.Build.props b/Directory.Build.props index be01849f..9dc62323 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,6 +6,7 @@ enable $(MSBuildThisFileDirectory).submodules\commands\ $(MSBuildThisFileDirectory).submodules\libplanet\ + $(MSBuildThisFileDirectory)src\shared\ $(NoWarn);NU1902;NU1903 diff --git a/src/client/LibplanetConsole.Client.Executable/Application.cs b/src/client/LibplanetConsole.Client.Executable/Application.cs deleted file mode 100644 index a0a20484..00000000 --- a/src/client/LibplanetConsole.Client.Executable/Application.cs +++ /dev/null @@ -1,64 +0,0 @@ -// using JSSoft.Commands.Extensions; -// using JSSoft.Terminals; -// using LibplanetConsole.Common.Extensions; -// using Microsoft.Extensions.DependencyInjection; - -// namespace LibplanetConsole.Client.Executable; - -// internal sealed class Application(IServiceProvider serviceProvider, ApplicationOptions options) -// : ApplicationBase(serviceProvider, options) -// { -// private readonly ApplicationOptions _options = options; -// private Task _waitInputTask = Task.CompletedTask; - -// protected override async Task OnRunAsync(CancellationToken cancellationToken) -// { -// if (_options.NoREPL != true) -// { -// var sw = new StringWriter(); -// var startupCondition = _options.NodeEndPoint is null && _options.ParentProcessId == 0; -// var commandContext = this.GetRequiredService(); -// var terminal = this.GetRequiredService(); -// commandContext.Out = sw; -// await base.OnRunAsync(cancellationToken); -// await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); -// await commandContext.ExecuteAsync(["--help"], cancellationToken: default); -// await sw.WriteLineAsync(); -// await commandContext.ExecuteAsync(args: [], cancellationToken: default); -// await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); -// commandContext.Out = Console.Out; -// await sw.WriteLineIfAsync(startupCondition, GetStartupMessage()); -// await Console.Out.WriteAsync(sw.ToString()); - -// await terminal.StartAsync(cancellationToken); -// } -// else if (_options.ParentProcessId == 0) -// { -// await base.OnRunAsync(cancellationToken); -// _waitInputTask = WaitInputAsync(); -// } -// else -// { -// await base.OnRunAsync(cancellationToken); -// } -// } - -// protected override async ValueTask OnDisposeAsync() -// { -// await base.OnDisposeAsync(); -// await _waitInputTask; -// } - -// private static string GetStartupMessage() -// { -// var startText = TerminalStringBuilder.GetString("start", TerminalColorType.Green); -// return $"\nType '{startText} ' to connect to the node."; -// } - -// private async Task WaitInputAsync() -// { -// await Console.Out.WriteLineAsync("Press any key to exit."); -// await Task.Run(() => Console.ReadKey(intercept: true)); -// Cancel(); -// } -// } diff --git a/src/client/LibplanetConsole.Client.Executable/EntryCommands/RunCommand.cs b/src/client/LibplanetConsole.Client.Executable/EntryCommands/RunCommand.cs index b68b4199..0e969502 100644 --- a/src/client/LibplanetConsole.Client.Executable/EntryCommands/RunCommand.cs +++ b/src/client/LibplanetConsole.Client.Executable/EntryCommands/RunCommand.cs @@ -1,6 +1,5 @@ using JSSoft.Commands; using LibplanetConsole.Framework; -using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Client.Executable.EntryCommands; diff --git a/src/client/LibplanetConsole.Client.Executable/EntryCommands/StartCommand.cs b/src/client/LibplanetConsole.Client.Executable/EntryCommands/StartCommand.cs index 38575e3d..7c012b14 100644 --- a/src/client/LibplanetConsole.Client.Executable/EntryCommands/StartCommand.cs +++ b/src/client/LibplanetConsole.Client.Executable/EntryCommands/StartCommand.cs @@ -5,7 +5,6 @@ using LibplanetConsole.Framework; using LibplanetConsole.Settings; using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Client.Executable.EntryCommands; @@ -52,19 +51,19 @@ protected override async Task OnExecuteAsync(CancellationToken cancellationToken { // Setup a HTTP/2 endpoint without TLS. options.ListenLocalhost(port, o => o.Protocols = HttpProtocols.Http2); - options.ListenLocalhost(port + 1, o => o.Protocols = HttpProtocols.Http1AndHttp2); + // options.ListenLocalhost(port + 1, o => o.Protocols = HttpProtocols.Http1AndHttp2); }); builder.Services.AddClient(applicationOptions); - builder.Services.AddApplication(applicationOptions); + builder.Services.AddExecutable(applicationOptions); - // builder.Services.AddGrpc(); + builder.Services.AddGrpc(); + builder.Services.AddGrpcReflection(); using var app = builder.Build(); + app.UseClient(); app.MapGet("/", () => "123"); - // app.UseAuthentication(); - // app.UseAuthorization(); var @out = Console.Out; await @out.WriteLineAsync(); diff --git a/src/client/LibplanetConsole.Client.Executable/ServiceCollectionExtensions.cs b/src/client/LibplanetConsole.Client.Executable/ServiceCollectionExtensions.cs index b3d62236..6e7621a4 100644 --- a/src/client/LibplanetConsole.Client.Executable/ServiceCollectionExtensions.cs +++ b/src/client/LibplanetConsole.Client.Executable/ServiceCollectionExtensions.cs @@ -1,18 +1,16 @@ using JSSoft.Commands; using LibplanetConsole.Client.Executable.Commands; using LibplanetConsole.Client.Executable.Tracers; -using LibplanetConsole.Framework; using LibplanetConsole.Logging; -using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Client.Executable; internal static class ServiceCollectionExtensions { - public static IServiceCollection AddApplication( + public static IServiceCollection AddExecutable( this IServiceCollection @this, ApplicationOptions options) { - @this.AddLogging(options.LogPath, string.Empty); + @this.AddLogging(options.LogPath, options.LogPath); @this.AddSingleton(); @this.AddSingleton(); diff --git a/src/client/LibplanetConsole.Client.Executable/TerminalHostedService.cs b/src/client/LibplanetConsole.Client.Executable/TerminalHostedService.cs index 9fde140f..904b7069 100644 --- a/src/client/LibplanetConsole.Client.Executable/TerminalHostedService.cs +++ b/src/client/LibplanetConsole.Client.Executable/TerminalHostedService.cs @@ -1,7 +1,7 @@ +using System.Diagnostics; using JSSoft.Commands.Extensions; using JSSoft.Terminals; using LibplanetConsole.Common.Extensions; -using Microsoft.Extensions.Hosting; namespace LibplanetConsole.Client.Executable; @@ -9,11 +9,14 @@ internal sealed class TerminalHostedService( IHostApplicationLifetime applicationLifetime, CommandContext commandContext, SystemTerminal terminal, - ApplicationOptions options) : IHostedService, IDisposable + ApplicationOptions options, + ILogger logger) : IHostedService, IDisposable { private readonly CancellationTokenSource _cancellationTokenSource = new(); private Task _runningTask = Task.CompletedTask; private Task _waitInputTask = Task.CompletedTask; + private Task _waitForExitTask = Task.CompletedTask; + private int _parentProcessId; public async Task StartAsync(CancellationToken cancellationToken) { @@ -39,6 +42,12 @@ public async Task StartAsync(CancellationToken cancellationToken) _cancellationTokenSource.Cancel(); }); } + else if (options.ParentProcessId != 0 && + Process.GetProcessById(options.ParentProcessId) is { } parentProcess) + { + _parentProcessId = options.ParentProcessId; + _waitForExitTask = WaitForExit(parentProcess); + } else if (options.ParentProcessId == 0) { _waitInputTask = WaitInputAsync(); @@ -49,8 +58,7 @@ public async Task StartAsync(CancellationToken cancellationToken) public async Task StopAsync(CancellationToken cancellationToken) { - await _runningTask; - await _waitInputTask; + await Task.WhenAll(_runningTask, _waitInputTask, _waitForExitTask); await terminal.StopAsync(cancellationToken); } @@ -78,4 +86,11 @@ private async Task WaitInputAsync() await Task.Run(() => Console.ReadKey(intercept: true)); applicationLifetime.StopApplication(); } + + private async Task WaitForExit(Process process) + { + await process.WaitForExitAsync(); + logger.LogDebug("Parent process is exited: {ParentProcessId}.", _parentProcessId); + applicationLifetime.StopApplication(); + } } diff --git a/src/client/LibplanetConsole.Client.Executable/Tracers/BlockChainEventTracer.cs b/src/client/LibplanetConsole.Client.Executable/Tracers/BlockChainEventTracer.cs index 21e3802e..377a9885 100644 --- a/src/client/LibplanetConsole.Client.Executable/Tracers/BlockChainEventTracer.cs +++ b/src/client/LibplanetConsole.Client.Executable/Tracers/BlockChainEventTracer.cs @@ -1,5 +1,5 @@ using JSSoft.Terminals; -using LibplanetConsole.Client; +using LibplanetConsole.Blockchain; using LibplanetConsole.Common.Extensions; namespace LibplanetConsole.Client.Executable.Tracers; diff --git a/src/client/LibplanetConsole.Client/Client.BlockChain.cs b/src/client/LibplanetConsole.Client/Client.BlockChain.cs index 406a00dd..a3740e12 100644 --- a/src/client/LibplanetConsole.Client/Client.BlockChain.cs +++ b/src/client/LibplanetConsole.Client/Client.BlockChain.cs @@ -1,7 +1,7 @@ using System.Security.Cryptography; using Grpc.Core; +using LibplanetConsole.Blockchain.Grpc; using LibplanetConsole.Node; -using LibplanetConsole.Node.Grpc; namespace LibplanetConsole.Client; @@ -36,6 +36,24 @@ public async Task SendTransactionAsync( return TxId.FromHex(response.TxId); } + public async Task SendTransactionAsync( + byte[] txData, CancellationToken cancellationToken) + { + if (_blockChainService is null) + { + throw new InvalidOperationException("BlockChainService is not initialized."); + } + + var request = new SendTransactionRequest + { + TransactionData = Google.Protobuf.ByteString.CopyFrom(txData), + }; + var callOptions = new CallOptions( + cancellationToken: cancellationToken); + var response = await _blockChainService.SendTransactionAsync(request, callOptions); + return TxId.FromHex(response.TxId); + } + public async Task GetBlockHashAsync(long height, CancellationToken cancellationToken) { if (_blockChainService is null) @@ -130,6 +148,25 @@ public async Task GetStateByStateRootHashAsync( return _codec.Decode(response.StateData.ToByteArray()); } + public async Task GetActionAsync( + TxId txId, int actionIndex, CancellationToken cancellationToken) + { + if (_blockChainService is null) + { + throw new InvalidOperationException("BlockChainService is not initialized."); + } + + var request = new GetActionRequest + { + TxId = txId.ToHex(), + ActionIndex = actionIndex, + }; + var options = new CallOptions( + cancellationToken: cancellationToken); + var response = await _blockChainService.GetActionAsync(request, options); + return response.ActionData.ToByteArray(); + } + public async Task GetActionAsync( TxId txId, int actionIndex, CancellationToken cancellationToken) where T : IAction diff --git a/src/client/LibplanetConsole.Client/Client.cs b/src/client/LibplanetConsole.Client/Client.cs index 1b9d2360..e1fe082f 100644 --- a/src/client/LibplanetConsole.Client/Client.cs +++ b/src/client/LibplanetConsole.Client/Client.cs @@ -1,8 +1,10 @@ using Grpc.Net.Client; +using LibplanetConsole.Blockchain; +using LibplanetConsole.Blockchain.Grpc; using LibplanetConsole.Client.Grpc; -using LibplanetConsole.Common; using LibplanetConsole.Common.Extensions; using LibplanetConsole.Node; +using LibplanetConsole.Node.Grpc; using Microsoft.Extensions.Logging; namespace LibplanetConsole.Client; @@ -33,7 +35,7 @@ public Client(ILogger logger, ApplicationOptions options) public event EventHandler? Started; - public event EventHandler? Stopped; + public event EventHandler? Stopped; public PublicKey PublicKey { get; } @@ -73,25 +75,33 @@ public async Task StartAsync(CancellationToken cancellationToken) throw new InvalidOperationException("The client is already running."); } - await Task.Delay(1000); + if (_nodeEndPoint is null) + { + throw new InvalidOperationException($"{nameof(NodeEndPoint)} is not initialized."); + } - var address = $"http://{EndPointUtility.ToString(_nodeEndPoint)}"; - var channelOptions = new GrpcChannelOptions + var channel = NodeChannel.CreateChannel(_nodeEndPoint); + var nodeService = new NodeService(channel); + var blockChainService = new BlockChainService(channel); + nodeService.Started += (sender, e) => InvokeNodeStartedEvent(e); + nodeService.Stopped += (sender, e) => InvokeNodeStoppedEvent(); + blockChainService.BlockAppended += (sender, e) => InvokeBlockAppendedEvent(e); + try { - ThrowOperationCanceledOnCancellation = true, - MaxRetryAttempts = 1, - }; - _channel = GrpcChannel.ForAddress(address, channelOptions); + await nodeService.StartAsync(cancellationToken); + await blockChainService.StartAsync(cancellationToken); + } + catch + { + nodeService.Dispose(); + blockChainService.Dispose(); + throw; + } + _cancellationTokenSource = new(); - _nodeService = new NodeService(_channel); - _nodeService.Disconnected += NodeService_Disconnected; - _nodeService.Started += (sender, e) => InvokeNodeStartedEvent(e); - _nodeService.Stopped += (sender, e) => InvokeNodeStoppedEvent(); - _blockChainService = new BlockChainService(_channel); - _blockChainService.BlockAppended += (sender, e) => InvokeBlockAppendedEvent(e); - await Task.WhenAll( - _nodeService.StartAsync(cancellationToken), - _blockChainService.StartAsync(cancellationToken)); + _channel = channel; + _nodeService = nodeService; + _blockChainService = blockChainService; _info = _info with { NodeAddress = NodeInfo.Address }; IsRunning = true; _logger.LogDebug( @@ -114,7 +124,6 @@ public async Task StopAsync(CancellationToken cancellationToken) if (_nodeService is not null) { - _nodeService.Disconnected -= NodeService_Disconnected; await _nodeService.StopAsync(cancellationToken); _nodeService = null; } @@ -133,12 +142,12 @@ public async Task StopAsync(CancellationToken cancellationToken) IsRunning = false; _info = _info with { NodeAddress = default }; _logger.LogDebug("Client is stopped: {Address}", Address); - Stopped?.Invoke(this, new(StopReason.None)); + Stopped?.Invoke(this, EventArgs.Empty); } - public void InvokeNodeStartedEvent(NodeInfo nodeInfo) + public void InvokeNodeStartedEvent(NodeEventArgs e) { - NodeInfo = nodeInfo; + NodeInfo = e.NodeInfo; _info = _info with { NodeAddress = NodeInfo.Address }; } @@ -148,8 +157,8 @@ public void InvokeNodeStoppedEvent() _info = _info with { NodeAddress = default }; } - public void InvokeBlockAppendedEvent(BlockInfo blockInfo) - => BlockAppended?.Invoke(this, new BlockEventArgs(blockInfo)); + public void InvokeBlockAppendedEvent(BlockEventArgs e) + => BlockAppended?.Invoke(this, e); public async ValueTask DisposeAsync() { @@ -174,7 +183,7 @@ private void NodeService_Disconnected(object? sender, EventArgs e) _channel?.Dispose(); _channel = null; IsRunning = false; - Stopped?.Invoke(this, new(StopReason.None)); + Stopped?.Invoke(this, EventArgs.Empty); } } } diff --git a/src/client/LibplanetConsole.Client/ClientHostedService.cs b/src/client/LibplanetConsole.Client/ClientHostedService.cs index 652cd526..3f968dad 100644 --- a/src/client/LibplanetConsole.Client/ClientHostedService.cs +++ b/src/client/LibplanetConsole.Client/ClientHostedService.cs @@ -14,7 +14,6 @@ public Task StartAsync(CancellationToken cancellationToken) { applicationLifetime.ApplicationStarted.Register(async () => { - logger.LogInformation("Application started."); if (options.NodeEndPoint is not null) { logger.LogDebug("Client auto-starting"); diff --git a/src/node/LibplanetConsole.Node/Services/BlockChainGrpcServiceV1.cs b/src/client/LibplanetConsole.Client/Grpc/BlockChainGrpcServiceV1.cs similarity index 83% rename from src/node/LibplanetConsole.Node/Services/BlockChainGrpcServiceV1.cs rename to src/client/LibplanetConsole.Client/Grpc/BlockChainGrpcServiceV1.cs index 6549173a..dcd345ff 100644 --- a/src/node/LibplanetConsole.Node/Services/BlockChainGrpcServiceV1.cs +++ b/src/client/LibplanetConsole.Client/Grpc/BlockChainGrpcServiceV1.cs @@ -1,28 +1,25 @@ using Grpc.Core; -using LibplanetConsole.Node.Grpc; +using LibplanetConsole.Blockchain; +using LibplanetConsole.Blockchain.Grpc; +using LibplanetConsole.Grpc; using Microsoft.Extensions.Hosting; -namespace LibplanetConsole.Node.Services; +namespace LibplanetConsole.Client.Grpc; internal sealed class BlockChainGrpcServiceV1( - Node node, + Client client, IBlockChain blockChain, IHostApplicationLifetime applicationLifetime) : BlockChainGrpcService.BlockChainGrpcServiceBase { private static readonly Codec _codec = new(); - public override Task IsReady(IsReadyRequest request, ServerCallContext context) - { - return Task.Run(() => new IsReadyResponse { IsReady = node.IsRunning }); - } - public async override Task SendTransaction( SendTransactionRequest request, ServerCallContext context) { - var tx = Transaction.Deserialize(request.TransactionData.ToByteArray()); - await node.AddTransactionAsync(tx, context.CancellationToken); - return new SendTransactionResponse { TxId = tx.Id.ToHex() }; + var txData = request.TransactionData.ToByteArray(); + var txId = await client.SendTransactionAsync(txData, context.CancellationToken); + return new SendTransactionResponse { TxId = txId.ToHex() }; } public async override Task GetNextNonce( @@ -66,7 +63,7 @@ public override async Task GetAction( { var txId = TxId.FromHex(request.TxId); var actionIndex = request.ActionIndex; - var action = await node.GetActionAsync(txId, actionIndex, context.CancellationToken); + var action = await client.GetActionAsync(txId, actionIndex, context.CancellationToken); return new GetActionResponse { ActionData = Google.Protobuf.ByteString.CopyFrom(action) }; } diff --git a/src/node/LibplanetConsole.Node/Services/NodeGrpcServiceV1.cs b/src/client/LibplanetConsole.Client/Grpc/ClientGrpcServiceV1.cs similarity index 64% rename from src/node/LibplanetConsole.Node/Services/NodeGrpcServiceV1.cs rename to src/client/LibplanetConsole.Client/Grpc/ClientGrpcServiceV1.cs index 031d217c..dbf2acdf 100644 --- a/src/node/LibplanetConsole.Node/Services/NodeGrpcServiceV1.cs +++ b/src/client/LibplanetConsole.Client/Grpc/ClientGrpcServiceV1.cs @@ -1,12 +1,13 @@ using Grpc.Core; -using LibplanetConsole.Node.Grpc; +using LibplanetConsole.Common; +using LibplanetConsole.Grpc; using Microsoft.Extensions.Hosting; -namespace LibplanetConsole.Node.Services; +namespace LibplanetConsole.Client.Grpc; -internal sealed class NodeGrpcServiceV1( - IHostApplicationLifetime applicationLifetime, INode node) - : NodeGrpcService.NodeGrpcServiceBase +internal sealed class ClientGrpcServiceV1( + IHostApplicationLifetime applicationLifetime, IClient client) + : ClientGrpcService.ClientGrpcServiceBase { public override Task Ping(PingRequest request, ServerCallContext context) { @@ -15,13 +16,14 @@ public override Task Ping(PingRequest request, ServerCallContext c public override async Task Start(StartRequest request, ServerCallContext context) { - await node.StartAsync(context.CancellationToken); - return new StartResponse { NodeInfo = node.Info }; + client.NodeEndPoint = EndPointUtility.Parse(request.NodeEndPoint); + await client.StartAsync(context.CancellationToken); + return new StartResponse { ClientInfo = client.Info }; } public async override Task Stop(StopRequest request, ServerCallContext context) { - await node.StopAsync(context.CancellationToken); + await client.StopAsync(context.CancellationToken); return new StopResponse(); } @@ -29,7 +31,7 @@ public override Task GetInfo(GetInfoRequest request, ServerCall { GetInfoResponse Action() => new() { - NodeInfo = node.Info, + ClientInfo = client.Info, }; return Task.Run(Action, context.CancellationToken); @@ -42,9 +44,9 @@ public override async Task GetStartedStream( { var streamer = new EventStreamer( responseStream, - handler => node.Started += handler, - handler => node.Started -= handler, - () => new GetStartedStreamResponse { NodeInfo = node.Info }); + handler => client.Started += handler, + handler => client.Started -= handler, + () => new GetStartedStreamResponse { ClientInfo = client.Info }); await streamer.RunAsync(applicationLifetime, context.CancellationToken); } @@ -55,8 +57,8 @@ public override async Task GetStoppedStream( { var streamer = new EventStreamer( responseStream, - handler => node.Stopped += handler, - handler => node.Stopped -= handler); + handler => client.Stopped += handler, + handler => client.Stopped -= handler); await streamer.RunAsync(applicationLifetime, context.CancellationToken); } } diff --git a/src/client/LibplanetConsole.Client/Grpc/Connection.cs b/src/client/LibplanetConsole.Client/Grpc/Connection.cs deleted file mode 100644 index 33eb37e1..00000000 --- a/src/client/LibplanetConsole.Client/Grpc/Connection.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace LibplanetConsole.Client.Grpc; - -internal sealed class Connection(NodeService nodeService) - : ConnectionMonitor(nodeService, FuncAsync) -{ - private static async Task FuncAsync(NodeService client, CancellationToken cancellationToken) - { - await client.PingAsync(new(), cancellationToken: cancellationToken); - } -} diff --git a/src/client/LibplanetConsole.Client/Grpc/NodeChannel.cs b/src/client/LibplanetConsole.Client/Grpc/NodeChannel.cs new file mode 100644 index 00000000..5ce489a1 --- /dev/null +++ b/src/client/LibplanetConsole.Client/Grpc/NodeChannel.cs @@ -0,0 +1,49 @@ +using Grpc.Core; +using Grpc.Net.Client; +using Grpc.Net.Client.Configuration; +using LibplanetConsole.Common; + +namespace LibplanetConsole.Client.Grpc; + +internal static class NodeChannel +{ + private static readonly GrpcChannelOptions _channelOptions = new() + { + ThrowOperationCanceledOnCancellation = true, + MaxRetryAttempts = 10, + ServiceConfig = new() + { + MethodConfigs = + { + new MethodConfig + { + Names = + { + new MethodName + { + Service = "libplanet.console.node.v1.NodeGrpcService", + Method = "Ping", + }, + }, + RetryPolicy = new RetryPolicy + { + MaxAttempts = 5, + InitialBackoff = TimeSpan.FromSeconds(1), + MaxBackoff = TimeSpan.FromSeconds(5), + BackoffMultiplier = 1.5, + RetryableStatusCodes = + { + StatusCode.Unavailable, + }, + }, + }, + }, + }, + }; + + public static GrpcChannel CreateChannel(EndPoint endPoint) + { + var address = $"http://{EndPointUtility.ToString(endPoint)}"; + return GrpcChannel.ForAddress(address, _channelOptions); + } +} diff --git a/src/client/LibplanetConsole.Client/IBlockChain.cs b/src/client/LibplanetConsole.Client/IBlockChain.cs index 909d8eb7..b23a282e 100644 --- a/src/client/LibplanetConsole.Client/IBlockChain.cs +++ b/src/client/LibplanetConsole.Client/IBlockChain.cs @@ -1,4 +1,5 @@ using System.Security.Cryptography; +using LibplanetConsole.Blockchain; namespace LibplanetConsole.Client; diff --git a/src/client/LibplanetConsole.Client/IClient.cs b/src/client/LibplanetConsole.Client/IClient.cs index 26cd1d37..0436d3b9 100644 --- a/src/client/LibplanetConsole.Client/IClient.cs +++ b/src/client/LibplanetConsole.Client/IClient.cs @@ -7,7 +7,7 @@ public interface IClient : IVerifier { event EventHandler? Started; - event EventHandler? Stopped; + event EventHandler? Stopped; ClientInfo Info { get; } diff --git a/src/client/LibplanetConsole.Client/LibplanetConsole.Client.csproj b/src/client/LibplanetConsole.Client/LibplanetConsole.Client.csproj index dcbf870c..d1b758ad 100644 --- a/src/client/LibplanetConsole.Client/LibplanetConsole.Client.csproj +++ b/src/client/LibplanetConsole.Client/LibplanetConsole.Client.csproj @@ -5,10 +5,14 @@ - - - - + + + + + + + + diff --git a/src/client/LibplanetConsole.Client/NodeEndpointRouteBuilderExtensions.cs b/src/client/LibplanetConsole.Client/NodeEndpointRouteBuilderExtensions.cs new file mode 100644 index 00000000..7163208c --- /dev/null +++ b/src/client/LibplanetConsole.Client/NodeEndpointRouteBuilderExtensions.cs @@ -0,0 +1,16 @@ +using LibplanetConsole.Client.Grpc; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing; + +namespace LibplanetConsole.Client; + +public static class NodeEndpointRouteBuilderExtensions +{ + public static IEndpointRouteBuilder UseClient(this IEndpointRouteBuilder @this) + { + @this.MapGrpcService(); + @this.MapGrpcService(); + + return @this; + } +} diff --git a/src/common/LibplanetConsole.Common/Exceptions/InvalidOperationExceptionUtility.cs b/src/common/LibplanetConsole.Common/Exceptions/InvalidOperationExceptionUtility.cs deleted file mode 100644 index 027d85c1..00000000 --- a/src/common/LibplanetConsole.Common/Exceptions/InvalidOperationExceptionUtility.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace LibplanetConsole.Common.Exceptions; - -public static class InvalidOperationExceptionUtility -{ - public static void ThrowIf(bool condition, string message) - { - if (condition == true) - { - throw new InvalidOperationException(message); - } - } -} diff --git a/src/common/LibplanetConsole.Common/Services/ILocalService.cs b/src/common/LibplanetConsole.Common/Services/ILocalService.cs deleted file mode 100644 index 44c589d5..00000000 --- a/src/common/LibplanetConsole.Common/Services/ILocalService.cs +++ /dev/null @@ -1,8 +0,0 @@ -// using JSSoft.Communication; - -// namespace LibplanetConsole.Common.Services; - -// public interface ILocalService -// { -// IService Service { get; } -// } diff --git a/src/common/LibplanetConsole.Common/Services/IRemoteService.cs b/src/common/LibplanetConsole.Common/Services/IRemoteService.cs deleted file mode 100644 index d1d6f63e..00000000 --- a/src/common/LibplanetConsole.Common/Services/IRemoteService.cs +++ /dev/null @@ -1,8 +0,0 @@ -// using JSSoft.Communication; - -// namespace LibplanetConsole.Common.Services; - -// public interface IRemoteService -// { -// IService Service { get; } -// } diff --git a/src/common/LibplanetConsole.Common/Services/LocalService.cs b/src/common/LibplanetConsole.Common/Services/LocalService.cs deleted file mode 100644 index c3b25a53..00000000 --- a/src/common/LibplanetConsole.Common/Services/LocalService.cs +++ /dev/null @@ -1,62 +0,0 @@ -// // File may only contain a single type -// #pragma warning disable SA1402 -// using JSSoft.Communication; - -// namespace LibplanetConsole.Common.Services; - -// public class LocalService : ILocalService -// where TService : class -// where TCallback : class -// { -// private readonly ServerService _serverService; - -// public LocalService(TService service) -// { -// _serverService = new ServerService(service); -// } - -// public LocalService() -// { -// var obj = this; -// if (obj is TService service) -// { -// _serverService = new ServerService(service); -// } -// else -// { -// throw new InvalidOperationException( -// $"'{GetType()}' must be implemented by '{typeof(TService)}'."); -// } -// } - -// IService ILocalService.Service => _serverService; - -// protected TCallback Callback => _serverService.Client; -// } - -// public class LocalService : ILocalService -// where TService : class -// { -// private readonly ServerService _serverService; - -// public LocalService(TService service) -// { -// _serverService = new ServerService(service); -// } - -// public LocalService() -// { -// var obj = this; -// if (obj is TService service) -// { -// _serverService = new ServerService(service); -// } -// else -// { -// throw new InvalidOperationException( -// $"'{GetType()}' must be implemented by '{typeof(TService)}'."); -// } -// } - -// IService ILocalService.Service => _serverService; -// } diff --git a/src/common/LibplanetConsole.Common/Services/LocalServiceContext.cs b/src/common/LibplanetConsole.Common/Services/LocalServiceContext.cs deleted file mode 100644 index e944d3e2..00000000 --- a/src/common/LibplanetConsole.Common/Services/LocalServiceContext.cs +++ /dev/null @@ -1,69 +0,0 @@ -// using JSSoft.Communication; - -// namespace LibplanetConsole.Common.Services; - -// public class LocalServiceContext -// { -// private readonly InternalServerContext _serverContext; -// private EndPoint? _endPoint; - -// public LocalServiceContext(IEnumerable localServices) -// { -// _serverContext = new([.. localServices.Select(service => service.Service)]); -// _serverContext.Opened += (s, e) => Started?.Invoke(this, EventArgs.Empty); -// _serverContext.Closed += (s, e) -// => Stopped?.Invoke(this, new StopEventArgs(StopReason.None)); -// _serverContext.Disconnected += (s, e) -// => Stopped?.Invoke(this, new StopEventArgs(StopReason.Disconnected)); -// _serverContext.Faulted += (s, e) -// => Stopped?.Invoke(this, new StopEventArgs(StopReason.Faulted)); -// } - -// public event EventHandler? Started; - -// public event EventHandler? Stopped; - -// public EndPoint EndPoint -// { -// get => _endPoint ?? throw new InvalidOperationException("EndPoint is not set."); -// set -// { -// _endPoint = value; -// _serverContext.EndPoint = value; -// } -// } - -// public bool IsRunning => _serverContext.ServiceState == ServiceState.Open; - -// public async Task StartAsync(CancellationToken cancellationToken) -// { -// return await _serverContext.OpenAsync(cancellationToken); -// } - -// public Task StopAsync(Guid token) -// => CloseAsync(token, CancellationToken.None); - -// public async Task CloseAsync(Guid token, CancellationToken cancellationToken) -// { -// if (_serverContext.ServiceState == ServiceState.Open) -// { -// try -// { -// await _serverContext.CloseAsync(token, cancellationToken); -// } -// catch -// { -// // Ignore. -// } -// } - -// if (_serverContext.ServiceState == ServiceState.Faulted) -// { -// await _serverContext.AbortAsync(); -// } -// } - -// private sealed class InternalServerContext(IService[] services) : ServerContext(services) -// { -// } -// } diff --git a/src/common/LibplanetConsole.Common/Services/RemoteService.cs b/src/common/LibplanetConsole.Common/Services/RemoteService.cs deleted file mode 100644 index b7c134f8..00000000 --- a/src/common/LibplanetConsole.Common/Services/RemoteService.cs +++ /dev/null @@ -1,50 +0,0 @@ -// // File may only contain a single type -// #pragma warning disable SA1402 -// using JSSoft.Communication; - -// namespace LibplanetConsole.Common.Services; - -// public class RemoteService : IRemoteService -// where TService : class -// where TCallback : class -// { -// private readonly ClientService _clientService; - -// public RemoteService(TCallback callback) -// { -// _clientService = new ClientService(callback); -// } - -// public RemoteService() -// { -// var obj = this; -// if (obj is TCallback callback) -// { -// _clientService = new ClientService(callback); -// } -// else -// { -// throw new InvalidOperationException( -// $"'{GetType()}' must be implemented by '{typeof(TCallback)}'."); -// } -// } - -// public TService Service => _clientService.Server; - -// IService IRemoteService.Service => _clientService; -// } - -// public class RemoteService : IRemoteService -// where TService : class -// { -// private readonly ClientService _clientService; - -// public RemoteService() -// { -// _clientService = new ClientService(); -// } - -// public TService Service => _clientService.Server; - -// IService IRemoteService.Service => _clientService; -// } diff --git a/src/common/LibplanetConsole.Common/Services/RemoteServiceContext.cs b/src/common/LibplanetConsole.Common/Services/RemoteServiceContext.cs deleted file mode 100644 index 6619fe6b..00000000 --- a/src/common/LibplanetConsole.Common/Services/RemoteServiceContext.cs +++ /dev/null @@ -1,64 +0,0 @@ -// using JSSoft.Communication; - -// namespace LibplanetConsole.Common.Services; - -// public class RemoteServiceContext -// { -// private readonly InternalClientContext _clientContext; -// private EndPoint? _endPoint; - -// public RemoteServiceContext(IEnumerable remoteServices) -// { -// _clientContext = new([.. remoteServices.Select(service => service.Service)]); -// _clientContext.Opened += (s, e) => Opened?.Invoke(this, EventArgs.Empty); -// _clientContext.Closed += (s, e) => Closed?.Invoke(this, EventArgs.Empty); -// } - -// public event EventHandler? Opened; - -// public event EventHandler? Closed; - -// public EndPoint EndPoint -// { -// get => _endPoint ?? throw new InvalidOperationException("EndPoint is not set."); -// set -// { -// _endPoint = value; -// _clientContext.EndPoint = value; -// } -// } - -// public bool IsRunning => _clientContext.ServiceState == ServiceState.Open; - -// public async Task OpenAsync(CancellationToken cancellationToken) -// { -// return await _clientContext.OpenAsync(cancellationToken); -// } - -// public Task CloseAsync(Guid token) -// => CloseAsync(token, CancellationToken.None); - -// public async Task CloseAsync(Guid token, CancellationToken cancellationToken) -// { -// if (_clientContext.ServiceState == ServiceState.Open) -// { -// try -// { -// await _clientContext.CloseAsync(token, cancellationToken); -// } -// catch -// { -// // Ignore. -// } -// } - -// if (_clientContext.ServiceState == ServiceState.Faulted) -// { -// await _clientContext.AbortAsync(); -// } -// } - -// private sealed class InternalClientContext(IService[] services) : ClientContext(services) -// { -// } -// } diff --git a/src/common/LibplanetConsole.Common/StopEventArgs.cs b/src/common/LibplanetConsole.Common/StopEventArgs.cs deleted file mode 100644 index 71ae3128..00000000 --- a/src/common/LibplanetConsole.Common/StopEventArgs.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace LibplanetConsole.Common; - -public sealed class StopEventArgs(StopReason reason) : EventArgs -{ - public StopReason Reason { get; } = reason; -} diff --git a/src/common/LibplanetConsole.Common/StopReason.cs b/src/common/LibplanetConsole.Common/StopReason.cs deleted file mode 100644 index 95b757da..00000000 --- a/src/common/LibplanetConsole.Common/StopReason.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace LibplanetConsole.Common; - -public enum StopReason -{ - /// - /// When the service shut down normally. - /// - None, - - /// - /// When the server is shut down. - /// - Disconnected, - - /// - /// When the server disconnects for unknown reasons. - /// - Faulted, -} diff --git a/src/common/LibplanetConsole.Common/Threading/TaskUtility.cs b/src/common/LibplanetConsole.Common/Threading/TaskUtility.cs index 69cf3858..289dfe9a 100644 --- a/src/common/LibplanetConsole.Common/Threading/TaskUtility.cs +++ b/src/common/LibplanetConsole.Common/Threading/TaskUtility.cs @@ -15,4 +15,18 @@ public static async Task TryDelay( return false; } } + + public static async Task TryDelay( + TimeSpan delay, CancellationToken cancellationToken) + { + try + { + await Task.Delay(delay, cancellationToken); + return true; + } + catch (TaskCanceledException) + { + return false; + } + } } diff --git a/src/common/LibplanetConsole.Framework/ApplicationFramework.cs b/src/common/LibplanetConsole.Framework/ApplicationFramework.cs deleted file mode 100644 index 13fcdc43..00000000 --- a/src/common/LibplanetConsole.Framework/ApplicationFramework.cs +++ /dev/null @@ -1,107 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace LibplanetConsole.Framework; - -public abstract class ApplicationFramework : IAsyncDisposable, IServiceProvider -{ - private readonly IServiceProvider _serviceProvider; - private readonly ManualResetEvent _closeEvent = new(false); - private readonly SynchronizationContext _synchronizationContext; - private readonly CancellationTokenSource _cancellationTokenSource = new(); - private readonly ILogger _logger; - private bool _isDisposed; - - protected ApplicationFramework(IServiceProvider serviceProvider) - { - SynchronizationContext.SetSynchronizationContext(new()); - _logger = serviceProvider.GetRequiredService>(); - _serviceProvider = serviceProvider; - _synchronizationContext = SynchronizationContext.Current!; - } - - protected virtual bool CanClose => false; - - public Task InvokeAsync(Action action, CancellationToken cancellationToken) - { - return Task.Run(Action, cancellationToken); - - void Action() => _synchronizationContext.Send((state) => action(), null); - } - - public Task InvokeAsync(Func func, CancellationToken cancellationToken) - { - return Task.Run(Func, cancellationToken); - - T Func() - { - T result = default!; - _synchronizationContext.Send((state) => result = func(), null); - return result; - } - } - - public void Cancel() - { - _logger.LogDebug("Application canceled."); - _cancellationTokenSource.Cancel(); - _closeEvent.Set(); - } - - public async Task RunAsync() - { - ObjectDisposedException.ThrowIf(_isDisposed == true, this); - - _logger.LogDebug("Application running..."); - await OnRunAsync(_cancellationTokenSource.Token); - _logger.LogDebug("Application waiting for close..."); - await Task.Run(() => - { - while (CanClose != true && _closeEvent.WaitOne(1) != true) - { - // Wait for close. - } - }); - _logger.LogDebug("Application run completed."); - } - - public async ValueTask DisposeAsync() - { - if (_isDisposed is false) - { - _logger.LogDebug("Application disposing..."); - await OnDisposeAsync(); - _cancellationTokenSource.Dispose(); - _isDisposed = true; - GC.SuppressFinalize(this); - _logger.LogDebug("Application disposed."); - } - } - - public abstract object? GetService(Type serviceType); - - protected virtual async Task OnRunAsync(CancellationToken cancellationToken) - { - var applicationServices = Sort(_serviceProvider.GetServices()); - - for (var i = 0; i < applicationServices.Length; i++) - { - var serviceName = applicationServices[i].GetType().Name; - _logger.LogDebug("Application service initializing: {ServiceName}", serviceName); - await applicationServices[i].InitializeAsync(cancellationToken); - _logger.LogDebug("Application service initialized: {ServiceName}", serviceName); - } - } - - protected virtual ValueTask OnDisposeAsync() => ValueTask.CompletedTask; - - private static IApplicationService[] Sort(IEnumerable items) - { - return DependencyUtility.TopologicalSort(items, GetDependencies).ToArray(); - - IEnumerable GetDependencies(IApplicationService item) - { - return DependencyUtility.GetDependencies(item, items); - } - } -} diff --git a/src/common/LibplanetConsole.Framework/ApplicationServiceCollection.cs b/src/common/LibplanetConsole.Framework/ApplicationServiceCollection.cs index 9cb24446..78d4fa67 100644 --- a/src/common/LibplanetConsole.Framework/ApplicationServiceCollection.cs +++ b/src/common/LibplanetConsole.Framework/ApplicationServiceCollection.cs @@ -1,22 +1,9 @@ using System.Reflection; -using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Framework; -public sealed class ApplicationServiceCollection : ServiceCollection +public static class ApplicationServiceCollection { - public ApplicationServiceCollection() - { - } - - public ApplicationServiceCollection(ApplicationSettingsCollection settingsCollection) - { - foreach (var settings in settingsCollection) - { - this.AddSingleton(settings.GetType(), settings); - } - } - public static IEnumerable GetAssemblies() => GetAssemblies(Assembly.GetEntryAssembly()!); diff --git a/src/common/LibplanetConsole.Framework/ApplicationSettingsCollection.cs b/src/common/LibplanetConsole.Framework/ApplicationSettingsCollection.cs index 8f0fdd22..f956200b 100644 --- a/src/common/LibplanetConsole.Framework/ApplicationSettingsCollection.cs +++ b/src/common/LibplanetConsole.Framework/ApplicationSettingsCollection.cs @@ -12,7 +12,7 @@ public sealed class ApplicationSettingsCollection : IEnumerable public ApplicationSettingsCollection() { - var assemblies = ApplicationServiceCollection.GetAssemblies(); + var assemblies = GetAssemblies(Assembly.GetEntryAssembly()!); var query = from assembly in assemblies from type in assembly.GetTypes() where IsApplicationSettings(type) == true @@ -73,4 +73,16 @@ private static string GetName(Type type) return settingsAttribute.GetSettingsName(type); } + + private static IEnumerable GetAssemblies(Assembly assembly) + { + var directory = Path.GetDirectoryName(assembly.Location)!; + var files = Directory.GetFiles(directory, "LibplanetConsole.*.dll"); + string[] paths = + [ + assembly.Location, + .. files, + ]; + return [.. paths.Distinct().Order().Select(Assembly.LoadFrom)]; + } } diff --git a/src/common/LibplanetConsole.Framework/IApplicationService.cs b/src/common/LibplanetConsole.Framework/IApplicationService.cs deleted file mode 100644 index b49978c9..00000000 --- a/src/common/LibplanetConsole.Framework/IApplicationService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace LibplanetConsole.Framework; - -public interface IApplicationService -{ - Task InitializeAsync(CancellationToken cancellationToken); -} diff --git a/src/console/LibplanetConsole.Console.Executable/EntryCommands/InitializeCommand.cs b/src/console/LibplanetConsole.Console.Executable/EntryCommands/InitializeCommand.cs index e6831a39..6baef585 100644 --- a/src/console/LibplanetConsole.Console.Executable/EntryCommands/InitializeCommand.cs +++ b/src/console/LibplanetConsole.Console.Executable/EntryCommands/InitializeCommand.cs @@ -5,6 +5,7 @@ using LibplanetConsole.Common.Extensions; using LibplanetConsole.Common.IO; using LibplanetConsole.DataAnnotations; +using LibplanetConsole.Node; namespace LibplanetConsole.Console.Executable.EntryCommands; @@ -28,7 +29,11 @@ public InitializeCommand() [EndPoint] public string EndPoint { get; set; } = string.Empty; +#if DEBUG + [CommandProperty(InitValue = 1)] +#else [CommandProperty(InitValue = 4)] +#endif [CommandSummary("The number of nodes to create. If omitted, 4 nodes are created.\n" + "Mutually exclusive with '--nodes' option.")] [CommandPropertyExclusion(nameof(Nodes))] @@ -41,7 +46,11 @@ public InitializeCommand() [PrivateKeyArray] public string[] Nodes { get; init; } = []; +#if DEBUG + [CommandProperty(InitValue = 1)] +#else [CommandProperty(InitValue = 2)] +#endif [CommandSummary("The number of clients to create. If omitted, 2 clients are created.\n" + "Mutually exclusive with '--clients' option.")] [CommandPropertyExclusion(nameof(Clients))] @@ -133,6 +142,7 @@ private NodeOptions[] GetNodeOptions(ref EndPoint? prevEndPoint) foreach (var privateKey in privateKeys) { var endPoint = prevEndPoint ?? EndPointUtility.NextEndPoint(); + EndPointUtility.NextEndPoint(); var nodeOptions = new NodeOptions { EndPoint = endPoint, diff --git a/src/console/LibplanetConsole.Console.Executable/EntryCommands/RunCommand.cs b/src/console/LibplanetConsole.Console.Executable/EntryCommands/RunCommand.cs index bcb228aa..1808d77a 100644 --- a/src/console/LibplanetConsole.Console.Executable/EntryCommands/RunCommand.cs +++ b/src/console/LibplanetConsole.Console.Executable/EntryCommands/RunCommand.cs @@ -1,6 +1,5 @@ using JSSoft.Commands; using LibplanetConsole.Framework; -using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Console.Executable.EntryCommands; @@ -27,26 +26,26 @@ object ICustomCommandDescriptor.GetMemberOwner(CommandMemberDescriptor memberDes protected override async Task OnExecuteAsync(CancellationToken cancellationToken) { - try - { - var serviceCollection = new ApplicationServiceCollection(_settingsCollection); - var applicationOptions = _applicationSettings.ToOptions(); + // try + // { + // var serviceCollection = new ApplicationServiceCollection(_settingsCollection); + // var applicationOptions = _applicationSettings.ToOptions(); - serviceCollection.AddConsole(applicationOptions); - serviceCollection.AddApplication(applicationOptions); + // serviceCollection.AddConsole(applicationOptions); + // serviceCollection.AddApplication(applicationOptions); - await using var serviceProvider = serviceCollection.BuildServiceProvider(); - var @out = System.Console.Out; - var application = serviceProvider.GetRequiredService(); - await @out.WriteLineAsync(); - await application.RunAsync(); - await @out.WriteLineAsync("\u001b0"); - } - catch (CommandParsingException e) - { - e.Print(System.Console.Out); - Environment.Exit(1); - } + // await using var serviceProvider = serviceCollection.BuildServiceProvider(); + // var @out = System.Console.Out; + // var application = serviceProvider.GetRequiredService(); + // await @out.WriteLineAsync(); + // await application.RunAsync(); + // await @out.WriteLineAsync("\u001b0"); + // } + // catch (CommandParsingException e) + // { + // e.Print(System.Console.Out); + // Environment.Exit(1); + // } } private static Dictionary GetDescriptors(object[] options) diff --git a/src/console/LibplanetConsole.Console.Executable/EntryCommands/StartCommand.cs b/src/console/LibplanetConsole.Console.Executable/EntryCommands/StartCommand.cs index 99492820..25f07f1a 100644 --- a/src/console/LibplanetConsole.Console.Executable/EntryCommands/StartCommand.cs +++ b/src/console/LibplanetConsole.Console.Executable/EntryCommands/StartCommand.cs @@ -1,8 +1,9 @@ using JSSoft.Commands; +using LibplanetConsole.Common; using LibplanetConsole.DataAnnotations; using LibplanetConsole.Framework; using LibplanetConsole.Settings; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Server.Kestrel.Core; namespace LibplanetConsole.Console.Executable.EntryCommands; @@ -18,28 +19,55 @@ internal sealed class StartCommand : CommandAsyncBase protected override async Task OnExecuteAsync(CancellationToken cancellationToken) { - var resolver = new RepositoryPathResolver(); - var repositoryPath = Path.GetFullPath(RepositoryPath); - var settingsPath = resolver.GetSettingsPath(repositoryPath); - var applicationSettings = Load(settingsPath); - var applicationOptions = applicationSettings.ToOptions() with + try { - Nodes = Repository.LoadNodeOptions(repositoryPath, resolver), - Clients = Repository.LoadClientOptions(repositoryPath, resolver), - LogPath = applicationSettings.LogPath, - LibraryLogPath = applicationSettings.LibraryLogPath, - }; - var serviceCollection = new ApplicationServiceCollection(_settingsCollection); - - serviceCollection.AddConsole(applicationOptions); - serviceCollection.AddApplication(applicationOptions); - - await using var serviceProvider = serviceCollection.BuildServiceProvider(); - var @out = System.Console.Out; - await using var application = serviceProvider.GetRequiredService(); - await @out.WriteLineAsync(); - await application.RunAsync(); - await @out.WriteLineAsync("\u001b0"); + var builder = WebApplication.CreateBuilder(); + + var resolver = new RepositoryPathResolver(); + var repositoryPath = Path.GetFullPath(RepositoryPath); + var settingsPath = resolver.GetSettingsPath(repositoryPath); + var applicationSettings = Load(settingsPath); + var applicationOptions = applicationSettings.ToOptions() with + { + Nodes = Repository.LoadNodeOptions(repositoryPath, resolver), + Clients = Repository.LoadClientOptions(repositoryPath, resolver), + LogPath = applicationSettings.LogPath, + LibraryLogPath = applicationSettings.LibraryLogPath, + }; + + foreach (var settings in _settingsCollection) + { + builder.Services.AddSingleton(settings.GetType(), settings); + } + + var (_, port) = EndPointUtility.GetHostAndPort(applicationOptions.EndPoint); + builder.WebHost.ConfigureKestrel(options => + { + // Setup a HTTP/2 endpoint without TLS. + options.ListenLocalhost(port, o => o.Protocols = HttpProtocols.Http2); + // options.ListenLocalhost(port + 1, o => o.Protocols = HttpProtocols.Http1AndHttp2); + }); + + builder.Services.AddConsole(applicationOptions); + builder.Services.AddExecutable(applicationOptions); + + builder.Services.AddGrpc(); + builder.Services.AddGrpcReflection(); + + using var app = builder.Build(); + + app.UseConsole(); + app.MapGet("/", () => "Libplanet Console"); + + var @out = System.Console.Out; + await @out.WriteLineAsync(); + await app.RunAsync(cancellationToken); + } + catch (CommandParsingException e) + { + e.Print(System.Console.Out); + Environment.Exit(1); + } } private static ApplicationSettings Load(string settingsPath) diff --git a/src/console/LibplanetConsole.Console.Executable/Application.cs b/src/console/LibplanetConsole.Console.Executable/ExecutableHostedService.cs similarity index 52% rename from src/console/LibplanetConsole.Console.Executable/Application.cs rename to src/console/LibplanetConsole.Console.Executable/ExecutableHostedService.cs index 2ffd1cfb..658c72da 100644 --- a/src/console/LibplanetConsole.Console.Executable/Application.cs +++ b/src/console/LibplanetConsole.Console.Executable/ExecutableHostedService.cs @@ -1,23 +1,23 @@ using JSSoft.Commands.Extensions; using JSSoft.Terminals; using LibplanetConsole.Common.Extensions; -using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Console.Executable; -internal sealed partial class Application( - IServiceProvider serviceProvider, ApplicationOptions options) - : ApplicationBase(serviceProvider, options), IApplication +internal sealed class ExecutableHostedService( + IHostApplicationLifetime applicationLifetime, + CommandContext commandContext, + SystemTerminal terminal) : IHostedService, IDisposable { - protected override async Task OnRunAsync(CancellationToken cancellationToken) + private readonly CancellationTokenSource _cancellationTokenSource = new(); + private Task _runningTask = Task.CompletedTask; + + public async Task StartAsync(CancellationToken cancellationToken) { var message = "Welcome to console for Libplanet."; var sw = new StringWriter(); - var commandContext = this.GetRequiredService(); - var terminal = this.GetRequiredService(); commandContext.Out = sw; await System.Console.Out.WriteColoredLineAsync(message, TerminalColorType.BrightGreen); - await base.OnRunAsync(cancellationToken); await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); await commandContext.ExecuteAsync(["--help"], cancellationToken: default); await sw.WriteLineAsync(); @@ -25,6 +25,21 @@ protected override async Task OnRunAsync(CancellationToken cancellationToken) await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); commandContext.Out = System.Console.Out; await System.Console.Out.WriteAsync(sw.ToString()); + await terminal.StartAsync(cancellationToken); + applicationLifetime.ApplicationStopping.Register(() => + { + _cancellationTokenSource.Cancel(); + }); + + await Task.CompletedTask; } + + public async Task StopAsync(CancellationToken cancellationToken) + { + await _runningTask; + await terminal.StopAsync(cancellationToken); + } + + void IDisposable.Dispose() => _cancellationTokenSource.Dispose(); } diff --git a/src/console/LibplanetConsole.Console.Executable/LibplanetConsole.Console.Executable.csproj b/src/console/LibplanetConsole.Console.Executable/LibplanetConsole.Console.Executable.csproj index 652698a2..020bd0a4 100644 --- a/src/console/LibplanetConsole.Console.Executable/LibplanetConsole.Console.Executable.csproj +++ b/src/console/LibplanetConsole.Console.Executable/LibplanetConsole.Console.Executable.csproj @@ -1,4 +1,4 @@ - + diff --git a/src/console/LibplanetConsole.Console.Executable/NodeGenesisProcess.cs b/src/console/LibplanetConsole.Console.Executable/NodeGenesisProcess.cs index 9ae83f8b..d1d49499 100644 --- a/src/console/LibplanetConsole.Console.Executable/NodeGenesisProcess.cs +++ b/src/console/LibplanetConsole.Console.Executable/NodeGenesisProcess.cs @@ -1,4 +1,5 @@ using LibplanetConsole.Common; +using LibplanetConsole.Node; namespace LibplanetConsole.Console.Executable; diff --git a/src/console/LibplanetConsole.Console.Executable/Repository.cs b/src/console/LibplanetConsole.Console.Executable/Repository.cs index 3ee3d65b..681dc247 100644 --- a/src/console/LibplanetConsole.Console.Executable/Repository.cs +++ b/src/console/LibplanetConsole.Console.Executable/Repository.cs @@ -4,6 +4,7 @@ using LibplanetConsole.Common; using LibplanetConsole.Console.Extensions; using LibplanetConsole.Framework; +using LibplanetConsole.Node; namespace LibplanetConsole.Console.Executable; diff --git a/src/console/LibplanetConsole.Console.Executable/ServiceCollectionExtensions.cs b/src/console/LibplanetConsole.Console.Executable/ServiceCollectionExtensions.cs index 30902c5e..401a11af 100644 --- a/src/console/LibplanetConsole.Console.Executable/ServiceCollectionExtensions.cs +++ b/src/console/LibplanetConsole.Console.Executable/ServiceCollectionExtensions.cs @@ -2,32 +2,28 @@ using LibplanetConsole.Console.Evidence; using LibplanetConsole.Console.Executable.Commands; using LibplanetConsole.Console.Executable.Tracers; -using LibplanetConsole.Framework; using LibplanetConsole.Logging; -using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Console.Executable; internal static class ServiceCollectionExtensions { - public static IServiceCollection AddApplication( + public static IServiceCollection AddExecutable( this IServiceCollection @this, ApplicationOptions options) { @this.AddLogging(options.LogPath, options.LibraryLogPath); - @this.AddSingleton(s => new Application(s, options)); - @this.AddSingleton(s => s.GetRequiredService()); - @this.AddSingleton(); @this.AddSingleton(); + @this.AddHostedService(); @this.AddSingleton() .AddSingleton(s => s.GetRequiredService()); @this.AddSingleton() .AddSingleton(s => s.GetRequiredService()); - @this.AddSingleton(); - @this.AddSingleton(); + @this.AddHostedService(); + @this.AddHostedService(); @this.AddEvidence(); diff --git a/src/console/LibplanetConsole.Console.Executable/SystemTerminal.cs b/src/console/LibplanetConsole.Console.Executable/SystemTerminal.cs index fd13b278..98f2e363 100644 --- a/src/console/LibplanetConsole.Console.Executable/SystemTerminal.cs +++ b/src/console/LibplanetConsole.Console.Executable/SystemTerminal.cs @@ -7,11 +7,13 @@ internal sealed class SystemTerminal : SystemTerminalBase { private readonly CommandContext _commandContext; - public SystemTerminal(IApplication application, CommandContext commandContext) + public SystemTerminal( + IHostApplicationLifetime applicationLifetime, CommandContext commandContext) { _commandContext = commandContext; - _commandContext.Owner = application; + _commandContext.Owner = applicationLifetime; Prompt = "libplanet-console $ "; + applicationLifetime.ApplicationStopping.Register(() => Prompt = "\u001b0"); } protected override string FormatPrompt(string prompt) => prompt; diff --git a/src/console/LibplanetConsole.Console.Executable/Tracers/ClientCollectionEventTracer.cs b/src/console/LibplanetConsole.Console.Executable/Tracers/ClientCollectionEventTracer.cs index af6998be..bb7860aa 100644 --- a/src/console/LibplanetConsole.Console.Executable/Tracers/ClientCollectionEventTracer.cs +++ b/src/console/LibplanetConsole.Console.Executable/Tracers/ClientCollectionEventTracer.cs @@ -1,26 +1,31 @@ using System.Collections.Specialized; using JSSoft.Terminals; using LibplanetConsole.Common.Extensions; -using LibplanetConsole.Framework; namespace LibplanetConsole.Console.Executable.Tracers; -internal sealed class ClientCollectionEventTracer(IClientCollection clients) : IApplicationService +internal sealed class ClientCollectionEventTracer : IHostedService, IDisposable { - private readonly IClientCollection _clients = clients; + private readonly IClientCollection _clients; - public Task InitializeAsync(CancellationToken cancellationToken) + public ClientCollectionEventTracer(IClientCollection clients) { + _clients = clients; foreach (var client in _clients) { AttachEvent(client); } _clients.CollectionChanged += Clients_CollectionChanged; - return Task.CompletedTask; } - public ValueTask DisposeAsync() + public Task StartAsync(CancellationToken cancellationToken) + => Task.CompletedTask; + + public Task StopAsync(CancellationToken cancellationToken) + => Task.CompletedTask; + + void IDisposable.Dispose() { foreach (var client in _clients) { @@ -28,7 +33,6 @@ public ValueTask DisposeAsync() } _clients.CollectionChanged -= Clients_CollectionChanged; - return ValueTask.CompletedTask; } private void AttachEvent(IClient client) diff --git a/src/console/LibplanetConsole.Console.Executable/Tracers/NodeCollectionEventTracer.cs b/src/console/LibplanetConsole.Console.Executable/Tracers/NodeCollectionEventTracer.cs index 0b55b935..b1cfd005 100644 --- a/src/console/LibplanetConsole.Console.Executable/Tracers/NodeCollectionEventTracer.cs +++ b/src/console/LibplanetConsole.Console.Executable/Tracers/NodeCollectionEventTracer.cs @@ -1,17 +1,18 @@ using System.Collections.Specialized; using JSSoft.Terminals; +using LibplanetConsole.Blockchain; using LibplanetConsole.Common.Extensions; -using LibplanetConsole.Framework; namespace LibplanetConsole.Console.Executable.Tracers; -internal sealed class NodeCollectionEventTracer(INodeCollection nodes) : IApplicationService +internal sealed class NodeCollectionEventTracer : IHostedService, IDisposable { - private readonly INodeCollection _nodes = nodes; + private readonly INodeCollection _nodes; private INode? _current; - public Task InitializeAsync(CancellationToken cancellationToken) + public NodeCollectionEventTracer(INodeCollection nodes) { + _nodes = nodes; UpdateCurrent(_nodes.Current); foreach (var node in _nodes) { @@ -20,20 +21,23 @@ public Task InitializeAsync(CancellationToken cancellationToken) _nodes.CurrentChanged += Nodes_CurrentChanged; _nodes.CollectionChanged += Nodes_CollectionChanged; - return Task.CompletedTask; } - public ValueTask DisposeAsync() + public Task StartAsync(CancellationToken cancellationToken) + => Task.CompletedTask; + + public Task StopAsync(CancellationToken cancellationToken) + => Task.CompletedTask; + + void IDisposable.Dispose() { + _nodes.CurrentChanged -= Nodes_CurrentChanged; + _nodes.CollectionChanged -= Nodes_CollectionChanged; UpdateCurrent(null); foreach (var node in _nodes) { DetachEvent(node); } - - _nodes.CurrentChanged -= Nodes_CurrentChanged; - _nodes.CollectionChanged -= Nodes_CollectionChanged; - return ValueTask.CompletedTask; } private void UpdateCurrent(INode? node) diff --git a/src/console/LibplanetConsole.Console/ApplicationBase.cs b/src/console/LibplanetConsole.Console/ApplicationBase.cs deleted file mode 100644 index dd1ccf7a..00000000 --- a/src/console/LibplanetConsole.Console/ApplicationBase.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using LibplanetConsole.Framework; -using LibplanetConsole.Node; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace LibplanetConsole.Console; - -public abstract class ApplicationBase : ApplicationFramework, IApplication -{ - private readonly IServiceProvider _serviceProvider; - private readonly NodeCollection _nodes; - private readonly ClientCollection _clients; - private readonly ApplicationInfo _info; - private readonly ILogger _logger; - // private ConsoleServiceContext? _consoleContext; - private Guid _closeToken; - - protected ApplicationBase(IServiceProvider serviceProvider, ApplicationOptions options) - : base(serviceProvider) - { - _serviceProvider = serviceProvider; - _logger = serviceProvider.GetRequiredService>(); - _logger.LogDebug(Environment.CommandLine); - _logger.LogDebug("Application initializing..."); - _nodes = serviceProvider.GetRequiredService(); - _clients = serviceProvider.GetRequiredService(); - _info = new() - { - EndPoint = options.EndPoint, - LogPath = options.LogPath, - NoProcess = options.NoProcess, - Detach = options.Detach, - NewWindow = options.NewWindow, - }; - GenesisBlock = BlockUtility.DeserializeBlock(options.Genesis); - _logger.LogDebug("Application initialized."); - } - - public ApplicationInfo Info => _info; - - internal Block GenesisBlock { get; } - - public bool TryGetClient(string address, [MaybeNullWhen(false)] out IClient client) - { - if (address == string.Empty) - { - client = _clients.Current; - return client is not null; - } - - client = _clients.SingleOrDefault(item => IsEquals(item, address)); - return client is not null; - } - - public bool TryGetNode(string address, [MaybeNullWhen(false)] out INode node) - { - if (address == string.Empty) - { - node = _nodes.Current; - return node is not null; - } - - node = _nodes.SingleOrDefault(item => IsEquals(item, address)); - return node is not null; - } - - public IAddressable GetAddressable(string address) - => _nodes.Concat(_clients).Single(item => IsEquals(item, address)); - - public override object? GetService(Type serviceType) - => _serviceProvider.GetService(serviceType); - - IClient IApplication.GetClient(string address) => GetClient(address); - - INode IApplication.GetNode(string address) => GetNode(address); - - internal Client GetClient(string address) - { - if (address == string.Empty) - { - return _clients.Current ?? throw new InvalidOperationException("No node is selected."); - } - - return _clients.Single(item => IsEquals(item, address)); - } - - internal Node GetNode(string address) - { - if (address == string.Empty) - { - return _nodes.Current ?? throw new InvalidOperationException("No node is selected."); - } - - return _nodes.Single(item => IsEquals(item, address)); - } - - protected override async Task OnRunAsync(CancellationToken cancellationToken) - { - _logger.LogDebug("ConsoleContext is starting: {EndPoint}", _info.EndPoint); - // _consoleContext = _serviceProvider.GetRequiredService(); - // _consoleContext.EndPoint = _info.EndPoint; - // _closeToken = await _consoleContext.StartAsync(cancellationToken: default); - _logger.LogDebug("ConsoleContext is started: {EndPoint}", _info.EndPoint); - await base.OnRunAsync(cancellationToken); - } - - protected override async ValueTask OnDisposeAsync() - { - // if (_consoleContext is not null) - // { - // _logger.LogDebug("ConsoleContext is closing: {EndPoint}", _info.EndPoint); - // await _consoleContext.CloseAsync(_closeToken, CancellationToken.None); - // _consoleContext = null; - // _logger.LogDebug("ConsoleContext is closed: {EndPoint}", _info.EndPoint); - // } - - await base.OnDisposeAsync(); - } - - private static bool IsEquals(IAddressable addressable, string address) - => $"{addressable.Address}".StartsWith(address); -} diff --git a/src/console/LibplanetConsole.Console/ApplicationInfoProvider.cs b/src/console/LibplanetConsole.Console/ApplicationInfoProvider.cs index 15593bfe..116b5aa0 100644 --- a/src/console/LibplanetConsole.Console/ApplicationInfoProvider.cs +++ b/src/console/LibplanetConsole.Console/ApplicationInfoProvider.cs @@ -1,13 +1,24 @@ using LibplanetConsole.Common; +using Microsoft.Extensions.Hosting; namespace LibplanetConsole.Console; -internal sealed class ApplicationInfoProvider : InfoProviderBase +internal sealed class ApplicationInfoProvider : InfoProviderBase { - public ApplicationInfoProvider() + private readonly ApplicationInfo _info; + + public ApplicationInfoProvider(ApplicationOptions options) : base("Application") { + _info = new() + { + EndPoint = options.EndPoint, + LogPath = options.LogPath, + NoProcess = options.NoProcess, + Detach = options.Detach, + NewWindow = options.NewWindow, + }; } - protected override object? GetInfo(ApplicationBase obj) => obj.Info; + protected override object? GetInfo(IHostApplicationLifetime obj) => _info; } diff --git a/src/console/LibplanetConsole.Console/Client.BlockChain.cs b/src/console/LibplanetConsole.Console/Client.BlockChain.cs new file mode 100644 index 00000000..99c50032 --- /dev/null +++ b/src/console/LibplanetConsole.Console/Client.BlockChain.cs @@ -0,0 +1,161 @@ +using System.Security.Cryptography; +using Grpc.Core; +using LibplanetConsole.Blockchain; +using LibplanetConsole.Blockchain.Grpc; + +namespace LibplanetConsole.Console; + +internal sealed partial class Client +{ + private static readonly Codec _codec = new(); + + public event EventHandler? BlockAppended; + + public async Task GetNextNonceAsync(Address address, CancellationToken cancellationToken) + { + if (_blockChainService is null) + { + throw new InvalidOperationException("BlockChainService is not initialized."); + } + + var request = new GetNextNonceRequest + { + Address = address.ToHex(), + }; + var options = new CallOptions( + cancellationToken: cancellationToken); + var response = await _blockChainService.GetNextNonceAsync(request, options); + return response.Nonce; + } + + public async Task SendTransactionAsync( + IAction[] actions, CancellationToken cancellationToken) + { + if (_blockChainService is null) + { + throw new InvalidOperationException("BlockChainService is not initialized."); + } + + var address = _privateKey.Address; + var nonce = await GetNextNonceAsync(address, cancellationToken); + var genesisHash = _clientInfo.GenesisHash; + var tx = Transaction.Create( + nonce: nonce, + privateKey: _privateKey, + genesisHash: genesisHash, + actions: [.. actions.Select(item => item.PlainValue)]); + var txData = tx.Serialize(); + var request = new SendTransactionRequest + { + TransactionData = Google.Protobuf.ByteString.CopyFrom(txData), + }; + var callOptions = new CallOptions( + cancellationToken: cancellationToken); + var response = await _blockChainService.SendTransactionAsync(request, callOptions); + return TxId.FromHex(response.TxId); + } + + public async Task GetTipHashAsync(CancellationToken cancellationToken) + { + if (_blockChainService is null) + { + throw new InvalidOperationException("BlockChainService is not initialized."); + } + + var request = new GetTipHashRequest(); + var options = new CallOptions( + cancellationToken: cancellationToken); + var response = await _blockChainService.GetTipHashAsync(request, options); + return BlockHash.FromString(response.BlockHash); + } + + public async Task GetStateAsync( + BlockHash? blockHash, + Address accountAddress, + Address address, + CancellationToken cancellationToken) + { + if (_blockChainService is null) + { + throw new InvalidOperationException("BlockChainService is not initialized."); + } + + var request = new GetStateRequest + { + BlockHash = blockHash?.ToString() ?? string.Empty, + AccountAddress = accountAddress.ToHex(), + Address = address.ToHex(), + }; + var options = new CallOptions( + cancellationToken: cancellationToken); + var response = await _blockChainService.GetStateAsync(request, options); + return _codec.Decode(response.StateData.ToByteArray()); + } + + public async Task GetStateByStateRootHashAsync( + HashDigest stateRootHash, + Address accountAddress, + Address address, + CancellationToken cancellationToken) + { + if (_blockChainService is null) + { + throw new InvalidOperationException("BlockChainService is not initialized."); + } + + var request = new GetStateRequest + { + StateRootHash = stateRootHash.ToString(), + AccountAddress = accountAddress.ToHex(), + Address = address.ToHex(), + }; + var options = new CallOptions( + cancellationToken: cancellationToken); + var response = await _blockChainService.GetStateAsync(request, options); + return _codec.Decode(response.StateData.ToByteArray()); + } + + public async Task GetBlockHashAsync(long height, CancellationToken cancellationToken) + { + if (_blockChainService is null) + { + throw new InvalidOperationException("BlockChainService is not initialized."); + } + + var request = new GetBlockHashRequest + { + Height = height, + }; + var options = new CallOptions( + cancellationToken: cancellationToken); + var response = await _blockChainService.GetBlockHashAsync(request, options); + return BlockHash.FromString(response.BlockHash); + } + + public async Task GetActionAsync( + TxId txId, int actionIndex, CancellationToken cancellationToken) + where T : IAction + { + if (_blockChainService is null) + { + throw new InvalidOperationException("BlockChainService is not initialized."); + } + + var request = new GetActionRequest + { + TxId = txId.ToHex(), + ActionIndex = actionIndex, + }; + var options = new CallOptions( + cancellationToken: cancellationToken); + var response = await _blockChainService.GetActionAsync(request, options); + var value = _codec.Decode(response.ActionData.ToByteArray()); + if (Activator.CreateInstance(typeof(T)) is T action) + { + action.LoadPlainValue(value); + return action; + } + + throw new InvalidOperationException("Action not found."); + } +} diff --git a/src/console/LibplanetConsole.Console/Client.cs b/src/console/LibplanetConsole.Console/Client.cs index 1370eb36..3ce7533b 100644 --- a/src/console/LibplanetConsole.Console/Client.cs +++ b/src/console/LibplanetConsole.Console/Client.cs @@ -1,32 +1,36 @@ +using Grpc.Core; +using Grpc.Net.Client; +using LibplanetConsole.Blockchain; +using LibplanetConsole.Blockchain.Grpc; using LibplanetConsole.Client; -using LibplanetConsole.Client.Services; -using LibplanetConsole.Common.Exceptions; +using LibplanetConsole.Client.Grpc; +using LibplanetConsole.Common; using LibplanetConsole.Common.Extensions; -using Microsoft.Extensions.DependencyInjection; +using LibplanetConsole.Console.Grpc; using Microsoft.Extensions.Logging; namespace LibplanetConsole.Console; -internal sealed class Client : IClient, IClientCallback +internal sealed partial class Client : IClient, IBlockChain { private readonly IServiceProvider _serviceProvider; private readonly ClientOptions _clientOptions; - // private readonly RemoteService _remoteService; + private readonly PrivateKey _privateKey; private readonly ILogger _logger; private readonly CancellationTokenSource _processCancellationTokenSource = new(); - // private RemoteServiceContext? _remoteServiceContext; - private Guid _closeToken; + private ClientService? _clientService; + private BlockChainService? _blockChainService; + private GrpcChannel? _channel; private ClientInfo _clientInfo; private bool _isDisposed; - private bool _isInProgress; private ClientProcess? _process; private Task _processTask = Task.CompletedTask; public Client(IServiceProvider serviceProvider, ClientOptions clientOptions) { _serviceProvider = serviceProvider; + _privateKey = clientOptions.PrivateKey; _clientOptions = clientOptions; - // _remoteService = new(this); _logger = _serviceProvider.GetLogger(); PublicKey = clientOptions.PrivateKey.PublicKey; _logger.LogDebug("Client is created: {Address}", Address); @@ -46,7 +50,7 @@ public Client(IServiceProvider serviceProvider, ClientOptions clientOptions) public Address Address => PublicKey.Address; - public bool IsAttached => _closeToken != Guid.Empty; + public bool IsAttached { get; private set; } public bool IsRunning { get; private set; } @@ -63,35 +67,51 @@ public Client(IServiceProvider serviceProvider, ClientOptions clientOptions) public async Task GetInfoAsync(CancellationToken cancellationToken) { ObjectDisposedException.ThrowIf(_isDisposed, this); - InvalidOperationExceptionUtility.ThrowIf(IsRunning is not true, "Client is not running."); - // _clientInfo = await _remoteService.Service.GetInfoAsync(cancellationToken); - // return _clientInfo; - throw new NotImplementedException(); + if (_clientService is null) + { + throw new InvalidOperationException("Client is not attached."); + } + + var request = new GetInfoRequest(); + var callOptions = new CallOptions(cancellationToken: cancellationToken); + var response = await _clientService.GetInfoAsync(request, callOptions); + _clientInfo = response.ClientInfo; + return _clientInfo; } public async Task AttachAsync(CancellationToken cancellationToken) { ObjectDisposedException.ThrowIf(_isDisposed, this); - InvalidOperationExceptionUtility.ThrowIf( - condition: _closeToken != Guid.Empty, - message: "Client is already attached."); - - // if (_remoteServiceContext is not null) - // { - // throw new InvalidOperationException("Client is already attached."); - // } - - using var scope = new ProgressScope(this); - // _remoteServiceContext = new RemoteServiceContext( - // [_remoteService, .. GetRemoteServices(_serviceProvider)]) - // { - // EndPoint = _clientOptions.EndPoint, - // }; - // _closeToken = await _remoteServiceContext.OpenAsync(cancellationToken); - // _remoteServiceContext.Closed += RemoteServiceContext_Closed; - // _clientInfo = await _remoteService.Service.GetInfoAsync(cancellationToken); + if (_channel is not null) + { + throw new InvalidOperationException("Client is already attached."); + } + + var channel = ClientChannel.CreateChannel(_clientOptions.EndPoint); + var clientService = new ClientService(channel); + var blockChainService = new BlockChainService(channel); + clientService.Started += ClientService_Started; + clientService.Stopped += ClientService_Stopped; + blockChainService.BlockAppended += BlockChainService_BlockAppended; + try + { + await clientService.StartAsync(cancellationToken); + await blockChainService.StartAsync(cancellationToken); + } + catch + { + clientService.Dispose(); + blockChainService.Dispose(); + throw; + } + + _channel = channel; + _clientService = clientService; + _blockChainService = blockChainService; + _clientInfo = await GetInfoAsync(cancellationToken); IsRunning = _clientInfo.IsRunning; + IsAttached = true; _logger.LogDebug("Client is attached: {Address}", Address); Attached?.Invoke(this, EventArgs.Empty); } @@ -99,19 +119,33 @@ public async Task AttachAsync(CancellationToken cancellationToken) public async Task DetachAsync(CancellationToken cancellationToken) { ObjectDisposedException.ThrowIf(_isDisposed, this); - InvalidOperationExceptionUtility.ThrowIf( - condition: _closeToken == Guid.Empty, - message: "Client is not attached."); - - // if (_remoteServiceContext is null) - // { - // throw new InvalidOperationException("Client is not attached."); - // } - - // using var scope = new ProgressScope(this); - // _remoteServiceContext.Closed -= RemoteServiceContext_Closed; - // await _remoteServiceContext.CloseAsync(_closeToken, cancellationToken); - _closeToken = Guid.Empty; + + if (_channel is null) + { + throw new InvalidOperationException("Client is not attached."); + } + + if (_clientService is not null) + { + _clientService.Started -= ClientService_Started; + _clientService.Stopped -= ClientService_Stopped; + _clientService.Disconnected -= ClientService_Disconnected; + _clientService.Dispose(); + _clientService = null; + } + + if (_blockChainService is not null) + { + _blockChainService.BlockAppended -= BlockChainService_BlockAppended; + _blockChainService.Dispose(); + _blockChainService = null; + } + + await _channel.ShutdownAsync(); + _channel.Dispose(); + _channel = null; + IsRunning = false; + IsAttached = false; _logger.LogDebug("Client is detached: {Address}", Address); Detached?.Invoke(this, EventArgs.Empty); } @@ -119,13 +153,23 @@ public async Task DetachAsync(CancellationToken cancellationToken) public async Task StartAsync(INode node, CancellationToken cancellationToken) { ObjectDisposedException.ThrowIf(_isDisposed, this); - InvalidOperationExceptionUtility.ThrowIf(IsRunning is true, "Client is already running."); - InvalidOperationExceptionUtility.ThrowIf( - condition: _closeToken == Guid.Empty, - message: "Client is not attached."); + if (IsRunning is true) + { + throw new InvalidOperationException("Client is already running."); + } + + if (_clientService is null) + { + throw new InvalidOperationException("Client is not attached."); + } - // _clientInfo = await _remoteService.Service.StartAsync( - // EndPointUtility.ToString(node.EndPoint), cancellationToken); + var request = new StartRequest + { + NodeEndPoint = EndPointUtility.ToString(node.EndPoint), + }; + var callOptions = new CallOptions(cancellationToken: cancellationToken); + var response = await _clientService.StartAsync(request, callOptions); + _clientInfo = response.ClientInfo; IsRunning = true; _logger.LogDebug("Client is started: {Address}", Address); Started?.Invoke(this, EventArgs.Empty); @@ -134,30 +178,25 @@ public async Task StartAsync(INode node, CancellationToken cancellationToken) public async Task StopAsync(CancellationToken cancellationToken) { ObjectDisposedException.ThrowIf(_isDisposed, this); - InvalidOperationExceptionUtility.ThrowIf(IsRunning is not true, "Client is not running."); - InvalidOperationExceptionUtility.ThrowIf( - condition: _closeToken == Guid.Empty, - message: "Client is not attached."); - - // await _remoteService.Service.StopAsync(cancellationToken); - _closeToken = Guid.Empty; - _clientInfo = default; + if (IsRunning is false) + { + throw new InvalidOperationException("Client is not running."); + } + + if (_clientService is null) + { + throw new InvalidOperationException("Client is not attached."); + } + + var request = new StopRequest(); + var callOptions = new CallOptions(cancellationToken: cancellationToken); + await _clientService.StopAsync(request, callOptions); + _clientInfo = ClientInfo.Empty; IsRunning = false; _logger.LogDebug("Client is stopped: {Address}", Address); Stopped?.Invoke(this, EventArgs.Empty); } - public async Task SendTransactionAsync(string text, CancellationToken cancellationToken) - { - var transactionOptions = new TransactionOptions - { - Text = text, - }; - // return await _remoteService.Service.SendTransactionAsync( - // transactionOptions.Sign(this), cancellationToken); - throw new NotImplementedException(); - } - public async ValueTask DisposeAsync() { if (_isDisposed is false) @@ -168,14 +207,22 @@ public async ValueTask DisposeAsync() _processTask = Task.CompletedTask; _process = null; - // if (_remoteServiceContext is not null) - // { - // _remoteServiceContext.Closed -= RemoteServiceContext_Closed; - // await _remoteServiceContext.CloseAsync(_closeToken); - // _remoteServiceContext = null; - // } + if (_clientService is not null) + { + _clientService.Disconnected -= ClientService_Disconnected; + _clientService.Started -= ClientService_Started; + _clientService.Stopped -= ClientService_Stopped; + _clientService.Dispose(); + _clientService = null; + } + + if (_blockChainService is not null) + { + _blockChainService.BlockAppended -= BlockChainService_BlockAppended; + _blockChainService.Dispose(); + _blockChainService = null; + } - _closeToken = Guid.Empty; IsRunning = false; _isDisposed = true; _logger.LogDebug("Client is disposed: {Address}", Address); @@ -215,62 +262,47 @@ public Task StartProcessAsync(AddNewClientOptions options, CancellationToken can return Task.CompletedTask; } - void IClientCallback.OnStarted(ClientInfo clientInfo) + private void ClientService_Started(object? sender, ClientEventArgs e) { - if (_isInProgress is not true) - { - _clientInfo = clientInfo; - IsRunning = true; - Started?.Invoke(this, EventArgs.Empty); - } + _clientInfo = e.ClientInfo; + IsRunning = true; + Started?.Invoke(this, EventArgs.Empty); } - void IClientCallback.OnStopped() + private void ClientService_Stopped(object? sender, EventArgs e) { - if (_isInProgress is not true) - { - _clientInfo = default; - IsRunning = false; - Stopped?.Invoke(this, EventArgs.Empty); - } + Stopped?.Invoke(this, EventArgs.Empty); } - // private static IEnumerable GetRemoteServices( - // IServiceProvider serviceProvider) - // { - // foreach (var item in serviceProvider.GetServices()) - // { - // yield return item.RemoteService; - // } - // } - - // private void RemoteServiceContext_Closed(object? sender, EventArgs e) - // { - // if (sender is RemoteServiceContext remoteServiceContext) - // { - // remoteServiceContext.Closed -= RemoteServiceContext_Closed; - // _remoteServiceContext = null; - // if (_isInProgress is not true && IsRunning is true) - // { - // _closeToken = Guid.Empty; - // Detached?.Invoke(this, EventArgs.Empty); - // } - // } - // } - - private sealed class ProgressScope : IDisposable + private void BlockChainService_BlockAppended(object? sender, BlockEventArgs e) { - private readonly Client _client; - - public ProgressScope(Client client) - { - _client = client; - _client._isInProgress = true; - } + _clientInfo = _clientInfo with { TipHash = e.BlockInfo.Hash }; + BlockAppended?.Invoke(this, e); + } - public void Dispose() + private void ClientService_Disconnected(object? sender, EventArgs e) + { + if (sender is ClientService clientService && _clientService == clientService) { - _client._isInProgress = false; + _clientService.Disconnected -= ClientService_Disconnected; + _clientService.Started -= ClientService_Started; + _clientService.Stopped -= ClientService_Stopped; + _clientService.Dispose(); + _clientService = null; + if (_blockChainService is not null) + { + _blockChainService.BlockAppended -= BlockChainService_BlockAppended; + _blockChainService.Dispose(); + _blockChainService = null; + } + + if (_channel is not null) + { + _channel.Dispose(); + _channel = null; + } + + Detached?.Invoke(this, EventArgs.Empty); } } } diff --git a/src/console/LibplanetConsole.Console/ClientCollection.cs b/src/console/LibplanetConsole.Console/ClientCollection.cs index d341da9f..64e9b26a 100644 --- a/src/console/LibplanetConsole.Console/ClientCollection.cs +++ b/src/console/LibplanetConsole.Console/ClientCollection.cs @@ -3,20 +3,21 @@ using LibplanetConsole.Common.Extensions; using LibplanetConsole.Framework; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace LibplanetConsole.Console; [Dependency(typeof(NodeCollection))] internal sealed class ClientCollection( - IServiceProvider serviceProvider, ClientOptions[] clientOptions) - : IEnumerable, IClientCollection, IApplicationService, IAsyncDisposable + IServiceProvider serviceProvider, + ApplicationOptions options) + : IEnumerable, IClientCollection { private static readonly object LockObject = new(); - private readonly List _clientList = new(clientOptions.Length); + private readonly List _clientList = new(options.Clients.Length); private readonly ILogger _logger = serviceProvider.GetLogger(); private Client? _current; - private bool _isDisposed; public event EventHandler? CurrentChanged; @@ -120,39 +121,65 @@ public async Task AddNewAsync( return client; } - async Task IApplicationService.InitializeAsync(CancellationToken cancellationToken) + public async Task StartAsync(CancellationToken cancellationToken) { - var info = serviceProvider.GetRequiredService().Info; - await Parallel.ForAsync(0, _clientList.Capacity, cancellationToken, BodyAsync); + for (var i = 0; i < _clientList.Capacity; i++) + { + var client = ClientFactory.CreateNew(serviceProvider, options.Clients[i]); + InsertClient(client); + } + Current = _clientList.FirstOrDefault(); - async ValueTask BodyAsync(int index, CancellationToken cancellationToken) + await Task.CompletedTask; + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + for (var i = _clientList.Count - 1; i >= 0; i--) { - var options = new AddNewClientOptions - { - ClientOptions = clientOptions[index], - NoProcess = info.NoProcess, - Detach = info.Detach, - NewWindow = info.NewWindow, - }; - await AddNewAsync(options, cancellationToken); + var client = _clientList[i]!; + client.Disposed -= Client_Disposed; + await ClientFactory.DisposeScopeAsync(client); + _logger.LogDebug("Disposed a client: {Address}", client.Address); } } - async ValueTask IAsyncDisposable.DisposeAsync() + public async Task InitializeAsync(CancellationToken cancellationToken) { - if (_isDisposed is false) + try { - for (var i = _clientList.Count - 1; i >= 0; i--) + for (var i = 0; i < _clientList.Count; i++) { - var client = _clientList[i]!; - client.Disposed -= Client_Disposed; - await ClientFactory.DisposeScopeAsync(client); - _logger.LogDebug("Disposed a client: {Address}", client.Address); + var client = _clientList[i]; + var newOptions = new AddNewClientOptions + { + ClientOptions = options.Clients[i], + NoProcess = options.NoProcess, + Detach = options.Detach, + NewWindow = options.NewWindow, + }; + if (options.NoProcess != true) + { + await client.StartProcessAsync(newOptions, cancellationToken); + } + + if (options.NoProcess != true && options.Detach != true) + { + await client.AttachAsync(cancellationToken); + } + + if (client.IsAttached is true && newOptions.ClientOptions.NodeEndPoint is null) + { + var nodes = serviceProvider.GetRequiredService(); + var node = nodes.RandomNode(); + await client.StartAsync(node, cancellationToken); + } } - - _isDisposed = true; - GC.SuppressFinalize(this); + } + catch (Exception e) + { + _logger.LogError(e, "An error occurred while initializing nodes."); } } diff --git a/src/console/LibplanetConsole.Console/Commands/ClientCommand.cs b/src/console/LibplanetConsole.Console/Commands/ClientCommand.cs index 2304ba54..19685fba 100644 --- a/src/console/LibplanetConsole.Console/Commands/ClientCommand.cs +++ b/src/console/LibplanetConsole.Console/Commands/ClientCommand.cs @@ -1,13 +1,16 @@ using JSSoft.Commands; using JSSoft.Terminals; using LibplanetConsole.Common; +using LibplanetConsole.Common.Actions; using LibplanetConsole.Common.Extensions; +using LibplanetConsole.Console.Extensions; using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Console.Commands; [CommandSummary("Provides client-related commands.")] -public sealed partial class ClientCommand(ApplicationBase application, IClientCollection clients) +public sealed partial class ClientCommand( + IServiceProvider serviceProvider, IClientCollection clients) : CommandMethodBase { [CommandPropertyRequired(DefaultValue = "")] @@ -39,8 +42,8 @@ public void List() public void Info() { var address = Address; - var client = application.GetClient(address); - var clientInfo = InfoUtility.GetInfo(serviceProvider: application, obj: client); + var client = clients.GetClientOrCurrent(address); + var clientInfo = InfoUtility.GetInfo(serviceProvider, obj: client); Out.WriteLineAsJson(clientInfo); } @@ -74,7 +77,7 @@ public async Task NewAsync( public async Task DeleteAsync() { var address = Address; - var client = application.GetClient(address); + var client = clients.GetClientOrCurrent(address); await client.DisposeAsync(); } @@ -85,7 +88,7 @@ public async Task DeleteAsync() public async Task AttachAsync(CancellationToken cancellationToken = default) { var address = Address; - var client = application.GetClient(address); + var client = clients.GetClientOrCurrent(address); await client.AttachAsync(cancellationToken); } @@ -96,7 +99,7 @@ public async Task AttachAsync(CancellationToken cancellationToken = default) public async Task DetachAsync(CancellationToken cancellationToken = default) { var address = Address; - var client = application.GetClient(address); + var client = clients.GetClientOrCurrent(address); await client.DetachAsync(cancellationToken); } @@ -107,12 +110,10 @@ public async Task DetachAsync(CancellationToken cancellationToken = default) public async Task StartAsync( string nodeAddress = "", CancellationToken cancellationToken = default) { - var nodes = application.GetRequiredService(); + var nodes = serviceProvider.GetRequiredService(); var address = Address; - var node = nodeAddress == string.Empty - ? nodes.RandomNode() - : application.GetNode(nodeAddress); - var client = application.GetClient(address); + var node = nodes.GetNodeOrCurrent(nodeAddress); + var client = clients.GetClientOrCurrent(address); await client.StartAsync(node, cancellationToken); } @@ -123,7 +124,7 @@ public async Task StartAsync( public async Task StopAsync(CancellationToken cancellationToken) { var address = Address; - var client = application.GetClient(address); + var client = clients.GetClientOrCurrent(address); await client.StopAsync(cancellationToken); } @@ -134,7 +135,7 @@ public async Task StopAsync(CancellationToken cancellationToken) public void Current() { var address = Address; - if (address != string.Empty && application.GetClient(address) is { } client) + if (address != string.Empty && clients.GetClientOrCurrent(address) is { } client) { clients.Current = client; } @@ -159,8 +160,10 @@ public async Task TxAsync( CancellationToken cancellationToken) { var address = Address; - var client = application.GetClient(address); - await client.SendTransactionAsync(text, cancellationToken); + var client = clients.GetClientOrCurrent(address); + var blockChain = client.GetRequiredService(); + var action = new StringAction { Value = text }; + await blockChain.SendTransactionAsync([action], cancellationToken); await Out.WriteLineAsync($"{client.Address.ToShortString()}: {text}"); } diff --git a/src/console/LibplanetConsole.Console/Commands/ExitCommand.cs b/src/console/LibplanetConsole.Console/Commands/ExitCommand.cs index 65c5ef5d..793f0ed4 100644 --- a/src/console/LibplanetConsole.Console/Commands/ExitCommand.cs +++ b/src/console/LibplanetConsole.Console/Commands/ExitCommand.cs @@ -1,9 +1,23 @@ using JSSoft.Commands; +using Microsoft.Extensions.Hosting; namespace LibplanetConsole.Console.Commands; [CommandSummary("Exit the application.")] -internal sealed class ExitCommand(IApplication application) : CommandBase +internal sealed class ExitCommand(IHostApplicationLifetime applicationLifetime) + : CommandAsyncBase { - protected override void OnExecute() => application.Cancel(); + protected override async Task OnExecuteAsync(CancellationToken cancellationToken) + { + var resetEvent = new ManualResetEvent(false); + applicationLifetime.ApplicationStopping.Register(() => + { + resetEvent.Set(); + }); + applicationLifetime.StopApplication(); + while (!resetEvent.WaitOne(1)) + { + await Task.Delay(1, default); + } + } } diff --git a/src/console/LibplanetConsole.Console/Commands/InfoCommand.cs b/src/console/LibplanetConsole.Console/Commands/InfoCommand.cs index 43f8c772..6a2a5c03 100644 --- a/src/console/LibplanetConsole.Console/Commands/InfoCommand.cs +++ b/src/console/LibplanetConsole.Console/Commands/InfoCommand.cs @@ -1,15 +1,18 @@ using JSSoft.Commands; using LibplanetConsole.Common; using LibplanetConsole.Common.Extensions; +using Microsoft.Extensions.Hosting; namespace LibplanetConsole.Console.Commands; [CommandSummary("Print console application information.")] -internal sealed class InfoCommand(IApplication application) : CommandBase +internal sealed class InfoCommand( + IServiceProvider serviceProvider, IHostApplicationLifetime applicationLifetime) + : CommandBase { protected override void OnExecute() { - var info = InfoUtility.GetInfo(serviceProvider: application, obj: application); + var info = InfoUtility.GetInfo(serviceProvider: serviceProvider, obj: applicationLifetime); Out.WriteLineAsJson(info); } } diff --git a/src/console/LibplanetConsole.Console/Commands/NodeCommand.cs b/src/console/LibplanetConsole.Console/Commands/NodeCommand.cs index dc5f6490..e676883d 100644 --- a/src/console/LibplanetConsole.Console/Commands/NodeCommand.cs +++ b/src/console/LibplanetConsole.Console/Commands/NodeCommand.cs @@ -3,11 +3,13 @@ using LibplanetConsole.Common; using LibplanetConsole.Common.Actions; using LibplanetConsole.Common.Extensions; +using LibplanetConsole.Console.Extensions; +using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Console.Commands; [CommandSummary("Provides node-related commands.")] -public sealed partial class NodeCommand(ApplicationBase application, INodeCollection nodes) +public sealed partial class NodeCommand(IServiceProvider serviceProvider, INodeCollection nodes) : CommandMethodBase { [CommandPropertyRequired(DefaultValue = "")] @@ -38,7 +40,7 @@ public void List() public async Task DeleteAsync() { var address = Address; - var node = application.GetNode(address); + var node = nodes.GetNodeOrCurrent(address); await node.DisposeAsync(); } @@ -48,8 +50,8 @@ public async Task DeleteAsync() public void Info() { var address = Address; - var node = application.GetNode(address); - var nodeInfo = InfoUtility.GetInfo(serviceProvider: application, obj: node); + var node = nodes.GetNodeOrCurrent(address); + var nodeInfo = InfoUtility.GetInfo(serviceProvider, obj: node); Out.WriteLineAsJson(nodeInfo); } @@ -82,7 +84,7 @@ public async Task NewAsync( public async Task AttachAsync(CancellationToken cancellationToken = default) { var address = Address; - var node = application.GetNode(address); + var node = nodes.GetNodeOrCurrent(address); await node.AttachAsync(cancellationToken); } @@ -92,7 +94,7 @@ public async Task AttachAsync(CancellationToken cancellationToken = default) public async Task DetachAsync(CancellationToken cancellationToken = default) { var address = Address; - var node = application.GetNode(address); + var node = nodes.GetNodeOrCurrent(address); await node.DetachAsync(cancellationToken); } @@ -102,7 +104,7 @@ public async Task DetachAsync(CancellationToken cancellationToken = default) public async Task StartAsync(CancellationToken cancellationToken = default) { var address = Address; - var node = application.GetNode(address); + var node = nodes.GetNodeOrCurrent(address); await node.StartAsync(cancellationToken); } @@ -112,7 +114,7 @@ public async Task StartAsync(CancellationToken cancellationToken = default) public async Task StopAsync(CancellationToken cancellationToken = default) { var address = Address; - var node = application.GetNode(address); + var node = nodes.GetNodeOrCurrent(address); await node.StopAsync(cancellationToken); } @@ -121,7 +123,7 @@ public async Task StopAsync(CancellationToken cancellationToken = default) "If the address is not specified, displays the current node.")] public void Current(string address = "") { - if (address != string.Empty && application.GetNode(address) is { } node) + if (address != string.Empty && nodes.GetNodeOrCurrent(address) is { } node) { nodes.Current = node; } @@ -146,9 +148,10 @@ public async Task TxAsync( CancellationToken cancellationToken) { var address = Address; - var node = application.GetNode(address); + var node = nodes.GetNodeOrCurrent(address); + var blockChain = node.GetRequiredService(); var action = new StringAction { Value = text }; - await node.SendTransactionAsync([action], cancellationToken); + await blockChain.SendTransactionAsync([action], cancellationToken); await Out.WriteLineAsync($"{node.Address.ToShortString()}: {text}"); } diff --git a/src/console/LibplanetConsole.Console/Commands/TxCommand.cs b/src/console/LibplanetConsole.Console/Commands/TxCommand.cs index 528a0043..320fa5c5 100644 --- a/src/console/LibplanetConsole.Console/Commands/TxCommand.cs +++ b/src/console/LibplanetConsole.Console/Commands/TxCommand.cs @@ -6,7 +6,7 @@ namespace LibplanetConsole.Console.Commands; [CommandSummary("Sends a transaction using a simple string.")] -internal sealed class TxCommand(ApplicationBase application) : CommandAsyncBase +internal sealed class TxCommand(INodeCollection nodes, IClientCollection clients) : CommandAsyncBase { [CommandPropertyRequired] public string Address { get; set; } = string.Empty; @@ -16,7 +16,7 @@ internal sealed class TxCommand(ApplicationBase application) : CommandAsyncBase protected override async Task OnExecuteAsync(CancellationToken cancellationToken) { - var addressable = application.GetAddressable(Address); + var addressable = GetAddressable(new(Address)); var text = Text; if (addressable is INode node) { @@ -27,7 +27,9 @@ protected override async Task OnExecuteAsync(CancellationToken cancellationToken } else if (addressable is IClient client) { - await client.SendTransactionAsync(text, cancellationToken); + var blockChain = client.GetRequiredService(); + var action = new StringAction { Value = text }; + await blockChain.SendTransactionAsync([action], cancellationToken); await Out.WriteLineAsync($"{client.Address.ToShortString()}: {text}"); } else @@ -35,4 +37,19 @@ protected override async Task OnExecuteAsync(CancellationToken cancellationToken throw new InvalidOperationException("Invalid addressable."); } } + + private IAddressable GetAddressable(Address address) + { + if (nodes.Contains(address) is true) + { + return nodes[address]; + } + + if (clients.Contains(address) is true) + { + return clients[address]; + } + + throw new ArgumentException("Invalid address."); + } } diff --git a/src/console/LibplanetConsole.Console/ConsoleHostedService.cs b/src/console/LibplanetConsole.Console/ConsoleHostedService.cs new file mode 100644 index 00000000..ea4aaa8d --- /dev/null +++ b/src/console/LibplanetConsole.Console/ConsoleHostedService.cs @@ -0,0 +1,57 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace LibplanetConsole.Console; + +internal sealed class ConsoleHostedService( + SeedService seedService, + NodeCollection nodes, + ClientCollection clients, + IHostApplicationLifetime applicationLifetime, + ILogger logger) + : IHostedService +{ + private CancellationTokenSource? _cancellationTokenSource; + + public async Task StartAsync(CancellationToken cancellationToken) + { + applicationLifetime.ApplicationStarted.Register(Initialize); + await seedService.StartAsync(cancellationToken); + await nodes.StartAsync(cancellationToken); + await clients.StartAsync(cancellationToken); + + await Task.CompletedTask; + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + if (_cancellationTokenSource is not null) + { + await _cancellationTokenSource.CancelAsync(); + _cancellationTokenSource.Dispose(); + _cancellationTokenSource = null; + } + + await clients.StopAsync(cancellationToken); + await nodes.StopAsync(cancellationToken); + await seedService.StopAsync(cancellationToken); + } + + private async void Initialize() + { + _cancellationTokenSource = new(); + try + { + await nodes.InitializeAsync(_cancellationTokenSource.Token); + await clients.InitializeAsync(_cancellationTokenSource.Token); + } + catch (OperationCanceledException e) + { + logger.LogDebug(e, "The console was canceled."); + } + catch (Exception e) + { + logger.LogError(e, "An error occurred while starting the console."); + } + } +} diff --git a/src/console/LibplanetConsole.Console/ConsoleServiceContext.cs b/src/console/LibplanetConsole.Console/ConsoleServiceContext.cs deleted file mode 100644 index a46e711e..00000000 --- a/src/console/LibplanetConsole.Console/ConsoleServiceContext.cs +++ /dev/null @@ -1,8 +0,0 @@ -// using LibplanetConsole.Common.Services; - -// namespace LibplanetConsole.Console; - -// internal sealed class ConsoleServiceContext( -// IEnumerable localServices) : LocalServiceContext([.. localServices]) -// { -// } diff --git a/src/console/LibplanetConsole.Console/Extensions/IClientCollectionExtensions.cs b/src/console/LibplanetConsole.Console/Extensions/IClientCollectionExtensions.cs new file mode 100644 index 00000000..dad447f3 --- /dev/null +++ b/src/console/LibplanetConsole.Console/Extensions/IClientCollectionExtensions.cs @@ -0,0 +1,14 @@ +namespace LibplanetConsole.Console.Extensions; + +public static class IClientCollectionExtensions +{ + public static IClient GetClientOrCurrent(this IClientCollection @this, string address) + { + if (address == string.Empty) + { + return @this.Current ?? throw new InvalidOperationException("No client is selected."); + } + + return @this[new Address(address)]; + } +} diff --git a/src/console/LibplanetConsole.Console/Extensions/INodeCollectionExtensions.cs b/src/console/LibplanetConsole.Console/Extensions/INodeCollectionExtensions.cs new file mode 100644 index 00000000..b9001fef --- /dev/null +++ b/src/console/LibplanetConsole.Console/Extensions/INodeCollectionExtensions.cs @@ -0,0 +1,14 @@ +namespace LibplanetConsole.Console.Extensions; + +public static class INodeCollectionExtensions +{ + public static INode GetNodeOrCurrent(this INodeCollection @this, string address) + { + if (address == string.Empty) + { + return @this.Current ?? throw new InvalidOperationException("No node is selected."); + } + + return @this[new Address(address)]; + } +} diff --git a/src/console/LibplanetConsole.Console/Grpc/ClientChannel.cs b/src/console/LibplanetConsole.Console/Grpc/ClientChannel.cs new file mode 100644 index 00000000..055e7580 --- /dev/null +++ b/src/console/LibplanetConsole.Console/Grpc/ClientChannel.cs @@ -0,0 +1,49 @@ +using Grpc.Core; +using Grpc.Net.Client; +using Grpc.Net.Client.Configuration; +using LibplanetConsole.Common; + +namespace LibplanetConsole.Console.Grpc; + +internal static class ClientChannel +{ + private static readonly GrpcChannelOptions _channelOptions = new() + { + ThrowOperationCanceledOnCancellation = true, + MaxRetryAttempts = 10, + ServiceConfig = new() + { + MethodConfigs = + { + new MethodConfig + { + Names = + { + new MethodName + { + Service = "libplanet.console.client.v1.ClientGrpcService", + Method = "Ping", + }, + }, + RetryPolicy = new RetryPolicy + { + MaxAttempts = 5, + InitialBackoff = TimeSpan.FromSeconds(1), + MaxBackoff = TimeSpan.FromSeconds(5), + BackoffMultiplier = 1.5, + RetryableStatusCodes = + { + StatusCode.Unavailable, + }, + }, + }, + }, + }, + }; + + public static GrpcChannel CreateChannel(EndPoint endPoint) + { + var address = $"http://{EndPointUtility.ToString(endPoint)}"; + return GrpcChannel.ForAddress(address, _channelOptions); + } +} diff --git a/src/console/LibplanetConsole.Console/Grpc/NodeChannel.cs b/src/console/LibplanetConsole.Console/Grpc/NodeChannel.cs new file mode 100644 index 00000000..968fd1f9 --- /dev/null +++ b/src/console/LibplanetConsole.Console/Grpc/NodeChannel.cs @@ -0,0 +1,49 @@ +using Grpc.Core; +using Grpc.Net.Client; +using Grpc.Net.Client.Configuration; +using LibplanetConsole.Common; + +namespace LibplanetConsole.Console.Grpc; + +internal static class NodeChannel +{ + private static readonly GrpcChannelOptions _channelOptions = new() + { + ThrowOperationCanceledOnCancellation = true, + MaxRetryAttempts = 10, + ServiceConfig = new() + { + MethodConfigs = + { + new MethodConfig + { + Names = + { + new MethodName + { + Service = "libplanet.console.node.v1.NodeGrpcService", + Method = "Ping", + }, + }, + RetryPolicy = new RetryPolicy + { + MaxAttempts = 5, + InitialBackoff = TimeSpan.FromSeconds(1), + MaxBackoff = TimeSpan.FromSeconds(5), + BackoffMultiplier = 1.5, + RetryableStatusCodes = + { + StatusCode.Unavailable, + }, + }, + }, + }, + }, + }; + + public static GrpcChannel CreateChannel(EndPoint endPoint) + { + var address = $"http://{EndPointUtility.ToString(endPoint)}"; + return GrpcChannel.ForAddress(address, _channelOptions); + } +} diff --git a/src/node/LibplanetConsole.Node/Services/SeedGrpcServiceV1.cs b/src/console/LibplanetConsole.Console/Grpc/SeedGrpcServiceV1.cs similarity index 92% rename from src/node/LibplanetConsole.Node/Services/SeedGrpcServiceV1.cs rename to src/console/LibplanetConsole.Console/Grpc/SeedGrpcServiceV1.cs index 2886e272..12879ecb 100644 --- a/src/node/LibplanetConsole.Node/Services/SeedGrpcServiceV1.cs +++ b/src/console/LibplanetConsole.Console/Grpc/SeedGrpcServiceV1.cs @@ -2,7 +2,7 @@ using LibplanetConsole.Seed; using LibplanetConsole.Seed.Grpc; -namespace LibplanetConsole.Node.Services; +namespace LibplanetConsole.Console.Grpc; public sealed class SeedGrpcServiceV1(ISeedService seedService) : SeedGrpcService.SeedGrpcServiceBase diff --git a/src/console/LibplanetConsole.Console/IApplication.cs b/src/console/LibplanetConsole.Console/IApplication.cs deleted file mode 100644 index f11097fd..00000000 --- a/src/console/LibplanetConsole.Console/IApplication.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace LibplanetConsole.Console; - -public interface IApplication : IAsyncDisposable, IServiceProvider -{ - ApplicationInfo Info { get; } - - Task InvokeAsync(Action action, CancellationToken cancellationToken); - - Task InvokeAsync(Func func, CancellationToken cancellationToken); - - void Cancel(); - - IClient GetClient(string address); - - INode GetNode(string address); - - IAddressable GetAddressable(string address); - - IAddressable GetAddressable(Address address) - => GetAddressable(address.ToString()); -} diff --git a/src/console/LibplanetConsole.Console/IBlockChain.cs b/src/console/LibplanetConsole.Console/IBlockChain.cs index 44cb09c9..f6b6fe69 100644 --- a/src/console/LibplanetConsole.Console/IBlockChain.cs +++ b/src/console/LibplanetConsole.Console/IBlockChain.cs @@ -1,4 +1,5 @@ using System.Security.Cryptography; +using LibplanetConsole.Blockchain; namespace LibplanetConsole.Console; diff --git a/src/console/LibplanetConsole.Console/IClient.cs b/src/console/LibplanetConsole.Console/IClient.cs index 48755fe6..0267522b 100644 --- a/src/console/LibplanetConsole.Console/IClient.cs +++ b/src/console/LibplanetConsole.Console/IClient.cs @@ -32,6 +32,4 @@ public interface IClient : IAddressable, IAsyncDisposable, IServiceProvider, ISi Task StartAsync(INode node, CancellationToken cancellationToken); Task StopAsync(CancellationToken cancellationToken); - - Task SendTransactionAsync(string text, CancellationToken cancellationToken); } diff --git a/src/console/LibplanetConsole.Console/LibplanetConsole.Console.csproj b/src/console/LibplanetConsole.Console/LibplanetConsole.Console.csproj index a3f56ff5..4072164b 100644 --- a/src/console/LibplanetConsole.Console/LibplanetConsole.Console.csproj +++ b/src/console/LibplanetConsole.Console/LibplanetConsole.Console.csproj @@ -6,13 +6,19 @@ - - - + + + + + + + + + diff --git a/src/console/LibplanetConsole.Console/Node.BlockChain.cs b/src/console/LibplanetConsole.Console/Node.BlockChain.cs index cc7a7764..91240665 100644 --- a/src/console/LibplanetConsole.Console/Node.BlockChain.cs +++ b/src/console/LibplanetConsole.Console/Node.BlockChain.cs @@ -1,56 +1,72 @@ using System.Security.Cryptography; -using LibplanetConsole.Node; +using Grpc.Core; +using LibplanetConsole.Blockchain; +using LibplanetConsole.Blockchain.Grpc; namespace LibplanetConsole.Console; internal sealed partial class Node { + private static readonly Codec _codec = new(); + public event EventHandler? BlockAppended; - public Task GetNextNonceAsync(Address address, CancellationToken cancellationToken) + public async Task GetNextNonceAsync(Address address, CancellationToken cancellationToken) { - // return _blockChainService.Service.GetNextNonceAsync(address, cancellationToken); - throw new NotImplementedException(); + if (_blockChainService is null) + { + throw new InvalidOperationException("BlockChainService is not initialized."); + } + + var request = new GetNextNonceRequest + { + Address = address.ToHex(), + }; + var options = new CallOptions( + cancellationToken: cancellationToken); + var response = await _blockChainService.GetNextNonceAsync(request, options); + return response.Nonce; } public async Task SendTransactionAsync( IAction[] actions, CancellationToken cancellationToken) { - // var privateKey = _nodeOptions.PrivateKey; - // var address = privateKey.Address; - // var nonce = await _blockChainService.Service.GetNextNonceAsync(address, cancellationToken); - // var genesisHash = _nodeInfo.GenesisHash; - // var tx = Transaction.Create( - // nonce: nonce, - // privateKey: privateKey, - // genesisHash: genesisHash, - // actions: [.. actions.Select(item => item.PlainValue)]); - // var txId = await _blockChainService.Service.SendTransactionAsync( - // transaction: tx.Serialize(), - // cancellationToken: cancellationToken); - - // return txId; - throw new NotImplementedException(); - } + if (_blockChainService is null) + { + throw new InvalidOperationException("BlockChainService is not initialized."); + } - public Task SendTransactionAsync( - Transaction transaction, CancellationToken cancellationToken) - { - // return _blockChainService.Service.SendTransactionAsync( - // transaction.Serialize(), cancellationToken); - throw new NotImplementedException(); + var address = _privateKey.Address; + var nonce = await GetNextNonceAsync(address, cancellationToken); + var genesisHash = _nodeInfo.GenesisHash; + var tx = Transaction.Create( + nonce: nonce, + privateKey: _privateKey, + genesisHash: genesisHash, + actions: [.. actions.Select(item => item.PlainValue)]); + var txData = tx.Serialize(); + var request = new SendTransactionRequest + { + TransactionData = Google.Protobuf.ByteString.CopyFrom(txData), + }; + var callOptions = new CallOptions( + cancellationToken: cancellationToken); + var response = await _blockChainService.SendTransactionAsync(request, callOptions); + return TxId.FromHex(response.TxId); } - // void IBlockChainCallback.OnBlockAppended(BlockInfo blockInfo) - // { - // _nodeInfo = _nodeInfo with { TipHash = blockInfo.Hash }; - // BlockAppended?.Invoke(this, new BlockEventArgs(blockInfo)); - // } - - public Task GetTipHashAsync(CancellationToken cancellationToken) + public async Task GetTipHashAsync(CancellationToken cancellationToken) { - // return _blockChainService.Service.GetTipHashAsync(cancellationToken); - throw new NotImplementedException(); + if (_blockChainService is null) + { + throw new InvalidOperationException("BlockChainService is not initialized."); + } + + var request = new GetTipHashRequest(); + var options = new CallOptions( + cancellationToken: cancellationToken); + var response = await _blockChainService.GetTipHashAsync(request, options); + return BlockHash.FromString(response.BlockHash); } public async Task GetStateAsync( @@ -59,13 +75,21 @@ public async Task GetStateAsync( Address address, CancellationToken cancellationToken) { - // var bytes = await _blockChainService.Service.GetStateAsync( - // blockHash, - // accountAddress, - // address, - // cancellationToken); - // return _codec.Decode(bytes); - throw new NotImplementedException(); + if (_blockChainService is null) + { + throw new InvalidOperationException("BlockChainService is not initialized."); + } + + var request = new GetStateRequest + { + BlockHash = blockHash?.ToString() ?? string.Empty, + AccountAddress = accountAddress.ToHex(), + Address = address.ToHex(), + }; + var options = new CallOptions( + cancellationToken: cancellationToken); + var response = await _blockChainService.GetStateAsync(request, options); + return _codec.Decode(response.StateData.ToByteArray()); } public async Task GetStateByStateRootHashAsync( @@ -74,19 +98,38 @@ public async Task GetStateByStateRootHashAsync( Address address, CancellationToken cancellationToken) { - // var bytes = await _blockChainService.Service.GetStateByStateRootHashAsync( - // stateRootHash, - // accountAddress, - // address, - // cancellationToken); - // return _codec.Decode(bytes); - throw new NotImplementedException(); + if (_blockChainService is null) + { + throw new InvalidOperationException("BlockChainService is not initialized."); + } + + var request = new GetStateRequest + { + StateRootHash = stateRootHash.ToString(), + AccountAddress = accountAddress.ToHex(), + Address = address.ToHex(), + }; + var options = new CallOptions( + cancellationToken: cancellationToken); + var response = await _blockChainService.GetStateAsync(request, options); + return _codec.Decode(response.StateData.ToByteArray()); } - public Task GetBlockHashAsync(long height, CancellationToken cancellationToken) + public async Task GetBlockHashAsync(long height, CancellationToken cancellationToken) { - // return _blockChainService.Service.GetBlockHashAsync(height, cancellationToken); - throw new NotImplementedException(); + if (_blockChainService is null) + { + throw new InvalidOperationException("BlockChainService is not initialized."); + } + + var request = new GetBlockHashRequest + { + Height = height, + }; + var options = new CallOptions( + cancellationToken: cancellationToken); + var response = await _blockChainService.GetBlockHashAsync(request, options); + return BlockHash.FromString(response.BlockHash); } public async Task GetActionAsync( @@ -95,16 +138,26 @@ public async Task GetActionAsync( CancellationToken cancellationToken) where T : IAction { - // var bytes = await _blockChainService.Service.GetActionAsync( - // txId, actionIndex, cancellationToken); - // var value = _codec.Decode(bytes); - // if (Activator.CreateInstance(typeof(T)) is T action) - // { - // action.LoadPlainValue(value); - // return action; - // } - - // throw new InvalidOperationException("Action not found."); - throw new NotImplementedException(); + if (_blockChainService is null) + { + throw new InvalidOperationException("BlockChainService is not initialized."); + } + + var request = new GetActionRequest + { + TxId = txId.ToHex(), + ActionIndex = actionIndex, + }; + var options = new CallOptions( + cancellationToken: cancellationToken); + var response = await _blockChainService.GetActionAsync(request, options); + var value = _codec.Decode(response.ActionData.ToByteArray()); + if (Activator.CreateInstance(typeof(T)) is T action) + { + action.LoadPlainValue(value); + return action; + } + + throw new InvalidOperationException("Action not found."); } } diff --git a/src/console/LibplanetConsole.Console/Node.cs b/src/console/LibplanetConsole.Console/Node.cs index 60c8c487..cc2dd787 100644 --- a/src/console/LibplanetConsole.Console/Node.cs +++ b/src/console/LibplanetConsole.Console/Node.cs @@ -1,28 +1,32 @@ +using Grpc.Core; +using Grpc.Net.Client; +using LibplanetConsole.Blockchain; +using LibplanetConsole.Blockchain.Grpc; using LibplanetConsole.Common; -using LibplanetConsole.Common.Exceptions; using LibplanetConsole.Common.Extensions; +using LibplanetConsole.Console.Grpc; using LibplanetConsole.Node; +using LibplanetConsole.Node.Grpc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using NodeInfo = LibplanetConsole.Node.NodeInfo; namespace LibplanetConsole.Console; internal sealed partial class Node : INode, IBlockChain { - private static readonly Codec _codec = new(); private readonly IServiceProvider _serviceProvider; private readonly NodeOptions _nodeOptions; - // private readonly RemoteService _remoteService; - // private readonly RemoteService _blockChainService; + private readonly PrivateKey _privateKey; private readonly ILogger _logger; private readonly CancellationTokenSource _processCancellationTokenSource = new(); - // private RemoteServiceContext? _remoteServiceContext; + private NodeService? _nodeService; + private BlockChainService? _blockChainService; + private GrpcChannel? _channel; private EndPoint? _blocksyncEndPoint; private EndPoint? _consensusEndPoint; - private Guid _closeToken; private NodeInfo _nodeInfo; private bool _isDisposed; - private bool _isInProgress; private NodeProcess? _process; private Task _processTask = Task.CompletedTask; @@ -30,9 +34,8 @@ public Node(IServiceProvider serviceProvider, NodeOptions nodeOptions) { _serviceProvider = serviceProvider; _nodeOptions = nodeOptions; + _privateKey = nodeOptions.PrivateKey; _logger = _serviceProvider.GetLogger(); - // _remoteService = new(this); - // _blockChainService = new(this); PublicKey = nodeOptions.PrivateKey.PublicKey; _logger.LogDebug("Node is created: {Address}", Address); } @@ -57,7 +60,7 @@ public EndPoint ConsensusEndPoint public Address Address => PublicKey.Address; - public bool IsAttached => _closeToken != Guid.Empty; + public bool IsAttached { get; private set; } public bool IsRunning { get; private set; } @@ -74,36 +77,51 @@ public EndPoint ConsensusEndPoint public async Task GetInfoAsync(CancellationToken cancellationToken) { ObjectDisposedException.ThrowIf(_isDisposed, this); - InvalidOperationExceptionUtility.ThrowIf(IsRunning != true, "Node is not running."); - // _nodeInfo = await _remoteService.Service.GetInfoAsync(cancellationToken); - // return _nodeInfo; - throw new NotImplementedException(); + if (_nodeService is null) + { + throw new InvalidOperationException("Node is not attached."); + } + + var request = new GetInfoRequest(); + var callOptions = new CallOptions(cancellationToken: cancellationToken); + var response = await _nodeService.GetInfoAsync(request, callOptions); + _nodeInfo = response.NodeInfo; + return _nodeInfo; } public async Task AttachAsync(CancellationToken cancellationToken) { ObjectDisposedException.ThrowIf(_isDisposed, this); - InvalidOperationExceptionUtility.ThrowIf( - condition: _closeToken != Guid.Empty, - message: "Node is already attached."); - - // if (_remoteServiceContext is not null) - // { - // throw new InvalidOperationException("Node is already attached."); - // } - - // _remoteServiceContext = new RemoteServiceContext( - // [_remoteService, _blockChainService, .. GetRemoteServices(_serviceProvider)]) - // { - // EndPoint = _nodeOptions.EndPoint, - // }; - // _remoteServiceContext.Closed += RemoteServiceContext_Closed; - - using var scope = new ProgressScope(this); - // _closeToken = await _remoteServiceContext.OpenAsync(cancellationToken); - // _nodeInfo = await _remoteService.Service.GetInfoAsync(cancellationToken); + if (_channel is not null) + { + throw new InvalidOperationException("Node is already attached."); + } + + var channel = NodeChannel.CreateChannel(_nodeOptions.EndPoint); + var nodeService = new NodeService(channel); + var blockChainService = new BlockChainService(channel); + nodeService.Started += NodeService_Started; + nodeService.Stopped += NodeService_Stopped; + blockChainService.BlockAppended += BlockChainService_BlockAppended; + try + { + await nodeService.StartAsync(cancellationToken); + await blockChainService.StartAsync(cancellationToken); + } + catch + { + nodeService.Dispose(); + blockChainService.Dispose(); + throw; + } + + _channel = channel; + _nodeService = nodeService; + _blockChainService = blockChainService; + _nodeInfo = await GetInfoAsync(cancellationToken); IsRunning = _nodeInfo.IsRunning; + IsAttached = true; _logger.LogDebug("Node is attached: {Address}", Address); Attached?.Invoke(this, EventArgs.Empty); } @@ -111,20 +129,31 @@ public async Task AttachAsync(CancellationToken cancellationToken) public async Task DetachAsync(CancellationToken cancellationToken) { ObjectDisposedException.ThrowIf(_isDisposed, this); - InvalidOperationExceptionUtility.ThrowIf( - condition: _closeToken == Guid.Empty, - message: "Node is not attached."); - - // if (_remoteServiceContext is null) - // { - // throw new InvalidOperationException("Node is not attached."); - // } - - using var scope = new ProgressScope(this); - // _remoteServiceContext.Closed -= RemoteServiceContext_Closed; - // await _remoteServiceContext.CloseAsync(_closeToken, cancellationToken); - // _remoteServiceContext = null; - _closeToken = Guid.Empty; + if (_channel is null) + { + throw new InvalidOperationException("Node is not attached."); + } + + if (_nodeService is not null) + { + _nodeService.Started -= NodeService_Started; + _nodeService.Stopped -= NodeService_Stopped; + _nodeService.Dispose(); + _nodeService = null; + } + + if (_blockChainService is not null) + { + _blockChainService.BlockAppended -= BlockChainService_BlockAppended; + _blockChainService.Dispose(); + _blockChainService = null; + } + + await _channel.ShutdownAsync(); + _channel.Dispose(); + _channel = null; + IsRunning = false; + IsAttached = false; _logger.LogDebug("Node is detached: {Address}", Address); Detached?.Invoke(this, EventArgs.Empty); } @@ -132,16 +161,26 @@ public async Task DetachAsync(CancellationToken cancellationToken) public async Task StartAsync(CancellationToken cancellationToken) { ObjectDisposedException.ThrowIf(_isDisposed, this); - InvalidOperationExceptionUtility.ThrowIf(IsRunning == true, "Node is already running."); - InvalidOperationExceptionUtility.ThrowIf( - condition: _closeToken == Guid.Empty, - message: "Node is not attached."); + if (IsRunning is true) + { + throw new InvalidOperationException("Node is already running."); + } - using var scope = new ProgressScope(this); - var application = this.GetRequiredService(); + if (_nodeService is null) + { + throw new InvalidOperationException("Node is not attached."); + } + + var applicationOptions = this.GetRequiredService(); var seedEndPoint = EndPointUtility.ToString( - _nodeOptions.SeedEndPoint ?? application.Info.EndPoint); - // _nodeInfo = await _remoteService.Service.StartAsync(seedEndPoint, cancellationToken); + _nodeOptions.SeedEndPoint ?? applicationOptions.EndPoint); + var request = new StartRequest + { + SeedEndPoint = seedEndPoint, + }; + 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); IsRunning = true; @@ -152,14 +191,19 @@ public async Task StartAsync(CancellationToken cancellationToken) public async Task StopAsync(CancellationToken cancellationToken) { ObjectDisposedException.ThrowIf(_isDisposed, this); - InvalidOperationExceptionUtility.ThrowIf(IsRunning != true, "Node is not running."); - InvalidOperationExceptionUtility.ThrowIf( - condition: _closeToken == Guid.Empty, - message: "Node is not attached."); - - using var scope = new ProgressScope(this); - // await _remoteService.Service.StopAsync(cancellationToken); - _closeToken = Guid.Empty; + if (IsRunning is false) + { + throw new InvalidOperationException("Node is not running."); + } + + if (_nodeService is null) + { + throw new InvalidOperationException("Node is not attached."); + } + + var request = new StopRequest(); + var callOptions = new CallOptions(cancellationToken: cancellationToken); + await _nodeService.StopAsync(request, callOptions); _nodeInfo = NodeInfo.Empty; IsRunning = false; _logger.LogDebug("Node is stopped: {Address}", Address); @@ -176,14 +220,21 @@ public async ValueTask DisposeAsync() _processTask = Task.CompletedTask; _process = null; - // if (_remoteServiceContext is not null) - // { - // _remoteServiceContext.Closed -= RemoteServiceContext_Closed; - // await _remoteServiceContext.CloseAsync(_closeToken); - // _remoteServiceContext = null; - // } + if (_nodeService is not null) + { + _nodeService.Started -= NodeService_Started; + _nodeService.Stopped -= NodeService_Stopped; + _nodeService.Dispose(); + _nodeService = null; + } + + if (_blockChainService is not null) + { + _blockChainService.BlockAppended -= BlockChainService_BlockAppended; + _blockChainService.Dispose(); + _blockChainService = null; + } - _closeToken = Guid.Empty; IsRunning = false; _isDisposed = true; _logger.LogDebug("Node is disposed: {Address}", Address); @@ -221,62 +272,46 @@ public Task StartProcessAsync(AddNewNodeOptions options, CancellationToken cance return Task.CompletedTask; } - // void INodeCallback.OnStarted(NodeInfo nodeInfo) - // { - // if (_isInProgress != true) - // { - // _nodeInfo = nodeInfo; - // IsRunning = true; - // Started?.Invoke(this, EventArgs.Empty); - // } - // } - - // void INodeCallback.OnStopped() - // { - // if (_isInProgress != true) - // { - // _nodeInfo = NodeInfo.Empty; - // IsRunning = false; - // Stopped?.Invoke(this, EventArgs.Empty); - // } - // } - - // private static IEnumerable GetRemoteServices( - // IServiceProvider serviceProvider) - // { - // foreach (var item in serviceProvider.GetServices()) - // { - // yield return item.RemoteService; - // } - // } - - // private void RemoteServiceContext_Closed(object? sender, EventArgs e) - // { - // if (sender is RemoteServiceContext remoteServiceContext) - // { - // remoteServiceContext.Closed -= RemoteServiceContext_Closed; - // _remoteServiceContext = null; - // if (_isInProgress != true && IsRunning == true) - // { - // _closeToken = Guid.Empty; - // Detached?.Invoke(this, EventArgs.Empty); - // } - // } - // } - - private sealed class ProgressScope : IDisposable + private void NodeService_Started(object? sender, NodeEventArgs e) { - private readonly Node _node; + _nodeInfo = e.NodeInfo; + IsRunning = true; + Started?.Invoke(this, EventArgs.Empty); + } - public ProgressScope(Node node) - { - _node = node; - _node._isInProgress = true; - } + private void NodeService_Stopped(object? sender, EventArgs e) + { + Stopped?.Invoke(this, EventArgs.Empty); + } - public void Dispose() + private void BlockChainService_BlockAppended(object? sender, BlockEventArgs e) + { + _nodeInfo = _nodeInfo with { TipHash = e.BlockInfo.Hash }; + BlockAppended?.Invoke(this, e); + } + + private void NodeService_Disconnected(object? sender, EventArgs e) + { + if (sender is NodeService nodeService && _nodeService == nodeService) { - _node._isInProgress = false; + _nodeService.Started -= NodeService_Started; + _nodeService.Stopped -= NodeService_Stopped; + _nodeService.Dispose(); + _nodeService = null; + if (_blockChainService is not null) + { + _blockChainService.BlockAppended -= BlockChainService_BlockAppended; + _blockChainService.Dispose(); + _blockChainService = null; + } + + if (_channel is not null) + { + _channel.Dispose(); + _channel = null; + } + + Detached?.Invoke(this, EventArgs.Empty); } } } diff --git a/src/console/LibplanetConsole.Console/NodeCollection.cs b/src/console/LibplanetConsole.Console/NodeCollection.cs index bd829388..99cee42f 100644 --- a/src/console/LibplanetConsole.Console/NodeCollection.cs +++ b/src/console/LibplanetConsole.Console/NodeCollection.cs @@ -1,22 +1,19 @@ using System.Collections; using System.Collections.Specialized; using LibplanetConsole.Common.Extensions; -using LibplanetConsole.Framework; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace LibplanetConsole.Console; -// [Dependency(typeof(SeedService))] internal sealed class NodeCollection( - IServiceProvider serviceProvider, NodeOptions[] nodeOptions) - : IEnumerable, INodeCollection, IApplicationService, IAsyncDisposable + IServiceProvider serviceProvider, + ApplicationOptions options) + : IEnumerable, INodeCollection { private static readonly object LockObject = new(); - private readonly List _nodeList = new(nodeOptions.Length); + private readonly List _nodeList = new(options.Nodes.Length); private readonly ILogger _logger = serviceProvider.GetLogger(); private Node? _current; - private bool _isDisposed; public event EventHandler? CurrentChanged; @@ -118,39 +115,71 @@ public async Task AddNewAsync( return node; } - async Task IApplicationService.InitializeAsync(CancellationToken cancellationToken) + public async Task StartAsync(CancellationToken cancellationToken) { - var info = serviceProvider.GetRequiredService().Info; - await Parallel.ForAsync(0, _nodeList.Capacity, cancellationToken, BodyAsync); - Current = _nodeList.FirstOrDefault(); - - async ValueTask BodyAsync(int index, CancellationToken cancellationToken) + try { - var options = new AddNewNodeOptions + for (var i = 0; i < _nodeList.Capacity; i++) { - NodeOptions = nodeOptions[index], - NoProcess = info.NoProcess, - Detach = info.Detach, - NewWindow = info.NewWindow, - }; - await AddNewAsync(options, cancellationToken); + var node = NodeFactory.CreateNew(serviceProvider, options.Nodes[i]); + InsertNode(node); + } + + Current = _nodeList.FirstOrDefault(); + } + catch (Exception e) + { + _logger.LogError(e, "An error occurred while starting nodes."); } + + await Task.CompletedTask; } - async ValueTask IAsyncDisposable.DisposeAsync() + public async Task StopAsync(CancellationToken cancellationToken) { - if (_isDisposed is false) + for (var i = _nodeList.Count - 1; i >= 0; i--) { - for (var i = _nodeList.Count - 1; i >= 0; i--) + var node = _nodeList[i]!; + node.Disposed -= Node_Disposed; + await NodeFactory.DisposeScopeAsync(node); + _logger.LogDebug("Disposed a client: {Address}", node.Address); + } + } + + public async Task InitializeAsync(CancellationToken cancellationToken) + { + try + { + for (var i = 0; i < _nodeList.Count; i++) { - var node = _nodeList[i]!; - node.Disposed -= Node_Disposed; - await NodeFactory.DisposeScopeAsync(node); - _logger.LogDebug("Disposed a client: {Address}", node.Address); + var node = _nodeList[i]; + var newOptions = new AddNewNodeOptions + { + NodeOptions = options.Nodes[i], + NoProcess = options.NoProcess, + Detach = options.Detach, + NewWindow = options.NewWindow, + }; + + if (newOptions.NoProcess != true) + { + await node.StartProcessAsync(newOptions, cancellationToken); + } + + if (newOptions.NoProcess != true && newOptions.Detach != true) + { + await node.AttachAsync(cancellationToken); + } + + if (node.IsAttached is true && newOptions.NodeOptions.SeedEndPoint is null) + { + await node.StartAsync(cancellationToken); + } } - - _isDisposed = true; - GC.SuppressFinalize(this); + } + catch (Exception e) + { + _logger.LogError(e, "An error occurred while initializing nodes."); } } diff --git a/src/console/LibplanetConsole.Console/NodeEndpointRouteBuilderExtensions.cs b/src/console/LibplanetConsole.Console/NodeEndpointRouteBuilderExtensions.cs new file mode 100644 index 00000000..94807b1a --- /dev/null +++ b/src/console/LibplanetConsole.Console/NodeEndpointRouteBuilderExtensions.cs @@ -0,0 +1,15 @@ +using LibplanetConsole.Console.Grpc; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing; + +namespace LibplanetConsole.Console; + +public static class NodeEndpointRouteBuilderExtensions +{ + public static IEndpointRouteBuilder UseConsole(this IEndpointRouteBuilder @this) + { + @this.MapGrpcService(); + + return @this; + } +} diff --git a/src/console/LibplanetConsole.Console/NodeProcess.cs b/src/console/LibplanetConsole.Console/NodeProcess.cs index 19f3d1bd..dd0a9ff0 100644 --- a/src/console/LibplanetConsole.Console/NodeProcess.cs +++ b/src/console/LibplanetConsole.Console/NodeProcess.cs @@ -59,12 +59,12 @@ public override string[] Arguments argumentList.AddRange(extendedArguments); } - if (NewWindow != true) + if (NewWindow is false) { argumentList.Add("--no-repl"); } - if (Detach != true) + if (Detach is false) { argumentList.Add("--parent"); argumentList.Add(Environment.ProcessId.ToString()); diff --git a/src/console/LibplanetConsole.Console/SeedService.cs b/src/console/LibplanetConsole.Console/SeedService.cs new file mode 100644 index 00000000..88d2a7f8 --- /dev/null +++ b/src/console/LibplanetConsole.Console/SeedService.cs @@ -0,0 +1,62 @@ +using LibplanetConsole.Seed; +using Microsoft.Extensions.Hosting; +using static LibplanetConsole.Common.EndPointUtility; + +namespace LibplanetConsole.Console; + +internal sealed class SeedService : ISeedService +{ + private readonly PrivateKey _seedNodePrivateKey = new(); + private SeedNode? _blocksyncSeedNode; + private SeedNode? _consensusSeedNode; + + public Task GetSeedAsync( + PublicKey publicKey, CancellationToken cancellationToken) + { + if (_blocksyncSeedNode is null || _consensusSeedNode is null) + { + throw new InvalidOperationException("The SeedService is not running."); + } + + var seedPeer = _blocksyncSeedNode.BoundPeer; + var consensusSeedPeer = _consensusSeedNode.BoundPeer; + var seedInfo = new SeedInfo + { + BlocksyncSeedPeer = seedPeer, + ConsensusSeedPeer = consensusSeedPeer, + }; + + return Task.Run(() => seedInfo, cancellationToken); + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + _blocksyncSeedNode = new SeedNode(new() + { + PrivateKey = _seedNodePrivateKey, + EndPoint = NextEndPoint(), + }); + _consensusSeedNode = new SeedNode(new() + { + PrivateKey = _seedNodePrivateKey, + EndPoint = NextEndPoint(), + }); + await _blocksyncSeedNode.StartAsync(cancellationToken); + await _consensusSeedNode.StartAsync(cancellationToken); + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + if (_blocksyncSeedNode is not null) + { + await _blocksyncSeedNode.StopAsync(cancellationToken: default); + _blocksyncSeedNode = null; + } + + if (_consensusSeedNode is not null) + { + await _consensusSeedNode.StopAsync(cancellationToken: default); + _consensusSeedNode = null; + } + } +} diff --git a/src/console/LibplanetConsole.Console/ServiceCollectionExtensions.cs b/src/console/LibplanetConsole.Console/ServiceCollectionExtensions.cs index 5fd402b2..b3466410 100644 --- a/src/console/LibplanetConsole.Console/ServiceCollectionExtensions.cs +++ b/src/console/LibplanetConsole.Console/ServiceCollectionExtensions.cs @@ -1,7 +1,7 @@ using JSSoft.Commands; using LibplanetConsole.Common; using LibplanetConsole.Console.Commands; -using LibplanetConsole.Framework; +using LibplanetConsole.Seed; using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Console; @@ -11,13 +11,14 @@ public static class ServiceCollectionExtensions public static IServiceCollection AddConsole( this IServiceCollection @this, ApplicationOptions options) { - @this.AddSingleton(s => (ApplicationBase)s.GetRequiredService()); - @this.AddSingleton(s => new NodeCollection(s, options.Nodes)) - .AddSingleton(s => s.GetRequiredService()) - .AddSingleton(s => s.GetRequiredService()); - @this.AddSingleton(s => new ClientCollection(s, options.Clients)) - .AddSingleton(s => s.GetRequiredService()) - .AddSingleton(s => s.GetRequiredService()); + @this.AddSingleton(options); + @this.AddSingleton() + .AddSingleton(s => s.GetRequiredService()); + @this.AddSingleton() + .AddSingleton(s => s.GetRequiredService()); + @this.AddSingleton() + .AddSingleton(s => s.GetRequiredService()); + @this.AddHostedService(); @this.AddScoped(NodeFactory.Create) .AddScoped(s => s.GetRequiredService()) @@ -25,11 +26,6 @@ public static IServiceCollection AddConsole( @this.AddScoped(ClientFactory.Create) .AddScoped(s => s.GetRequiredService()); - // @this.AddSingleton(); - // @this.AddSingleton() - // .AddSingleton(s => s.GetRequiredService()) - // .AddSingleton(s => s.GetRequiredService()); - @this.AddSingleton(); @this.AddSingleton(); diff --git a/src/console/LibplanetConsole.Console/Services/IClientContentService.cs b/src/console/LibplanetConsole.Console/Services/IClientContentService.cs deleted file mode 100644 index c6ee30b8..00000000 --- a/src/console/LibplanetConsole.Console/Services/IClientContentService.cs +++ /dev/null @@ -1,8 +0,0 @@ -// using LibplanetConsole.Common.Services; - -// namespace LibplanetConsole.Console.Services; - -// public interface IClientContentService -// { -// IRemoteService RemoteService { get; } -// } diff --git a/src/console/LibplanetConsole.Console/Services/INodeContentService.cs b/src/console/LibplanetConsole.Console/Services/INodeContentService.cs deleted file mode 100644 index 37ce3bc8..00000000 --- a/src/console/LibplanetConsole.Console/Services/INodeContentService.cs +++ /dev/null @@ -1,8 +0,0 @@ -// using LibplanetConsole.Common.Services; - -// namespace LibplanetConsole.Console.Services; - -// public interface INodeContentService -// { -// IRemoteService RemoteService { get; } -// } diff --git a/src/console/LibplanetConsole.Console/Services/SeedService.cs b/src/console/LibplanetConsole.Console/Services/SeedService.cs deleted file mode 100644 index 99c2965e..00000000 --- a/src/console/LibplanetConsole.Console/Services/SeedService.cs +++ /dev/null @@ -1,64 +0,0 @@ -// using Libplanet.Net; -// using LibplanetConsole.Common; -// using LibplanetConsole.Common.Services; -// using LibplanetConsole.Framework; -// using LibplanetConsole.Seed; - -// namespace LibplanetConsole.Console.Services; - -// internal sealed class SeedService : LocalService, -// ISeedService, IApplicationService, IAsyncDisposable -// { -// private readonly PrivateKey _seedNodePrivateKey = new(); -// private readonly SeedNode _blocksyncSeedNode; -// private readonly SeedNode _consensusSeedNode; -// private bool _isDisposed; - -// public SeedService(IApplication application) -// { -// _blocksyncSeedNode = new SeedNode(new() -// { -// PrivateKey = _seedNodePrivateKey, -// EndPoint = EndPointUtility.NextEndPoint(), -// }); -// _consensusSeedNode = new SeedNode(new() -// { -// PrivateKey = _seedNodePrivateKey, -// EndPoint = EndPointUtility.NextEndPoint(), -// }); -// } - -// public BoundPeer BlocksyncSeedPeer => _blocksyncSeedNode.BoundPeer; - -// public BoundPeer ConsensusSeedPeer => _consensusSeedNode.BoundPeer; - -// public async Task GetSeedAsync( -// PublicKey publicKey, CancellationToken cancellationToken) -// { -// var seedPeer = _blocksyncSeedNode.BoundPeer; -// var consensusSeedPeer = _consensusSeedNode.BoundPeer; -// var seedInfo = new SeedInfo -// { -// BlocksyncSeedPeer = seedPeer, -// ConsensusSeedPeer = consensusSeedPeer, -// }; - -// return await Task.Run(() => seedInfo, cancellationToken); -// } - -// async Task IApplicationService.InitializeAsync(CancellationToken cancellationToken) -// { -// await _blocksyncSeedNode.StartAsync(cancellationToken); -// await _consensusSeedNode.StartAsync(cancellationToken); -// } - -// async ValueTask IAsyncDisposable.DisposeAsync() -// { -// if (_isDisposed is false) -// { -// await _blocksyncSeedNode.StopAsync(cancellationToken: default); -// await _consensusSeedNode.StopAsync(cancellationToken: default); -// _isDisposed = true; -// } -// } -// } diff --git a/src/node/LibplanetConsole.Node.Evidence/Services/EvidenceServiceGrpcV1.cs b/src/node/LibplanetConsole.Node.Evidence/Services/EvidenceServiceGrpcV1.cs index f0a368e7..b6c2df2c 100644 --- a/src/node/LibplanetConsole.Node.Evidence/Services/EvidenceServiceGrpcV1.cs +++ b/src/node/LibplanetConsole.Node.Evidence/Services/EvidenceServiceGrpcV1.cs @@ -1,5 +1,4 @@ using Grpc.Core; -using LibplanetConsole.Evidence; using LibplanetConsole.Evidence.Grpc; namespace LibplanetConsole.Node.Evidence.Services; diff --git a/src/node/LibplanetConsole.Node.Executable/Application.cs b/src/node/LibplanetConsole.Node.Executable/Application.cs deleted file mode 100644 index 92b204a9..00000000 --- a/src/node/LibplanetConsole.Node.Executable/Application.cs +++ /dev/null @@ -1,73 +0,0 @@ -// using JSSoft.Commands.Extensions; -// using JSSoft.Terminals; -// using LibplanetConsole.Common.Extensions; -// using Microsoft.Extensions.DependencyInjection; - -// namespace LibplanetConsole.Node.Executable; - -// internal sealed class Application(IServiceProvider serviceProvider, ApplicationOptions options) -// : ApplicationBase(serviceProvider, options), IApplication -// { -// private readonly ApplicationOptions _options = options; -// private Task _waitInputTask = Task.CompletedTask; - -// protected override async Task OnRunAsync(CancellationToken cancellationToken) -// { -// if (_options.NoREPL != true) -// { -// var sw = new StringWriter(); -// var commandContext = this.GetRequiredService(); -// var terminal = this.GetRequiredService(); -// commandContext.Out = sw; -// await base.OnRunAsync(cancellationToken); -// await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); -// await commandContext.ExecuteAsync(["--help"], cancellationToken: default); -// await sw.WriteLineAsync(); -// await commandContext.ExecuteAsync(args: [], cancellationToken: default); -// await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); -// commandContext.Out = Console.Out; -// await sw.WriteLineIfAsync(GetStartupCondition(_options), GetStartupMessage()); -// await Console.Out.WriteAsync(sw.ToString()); - -// await terminal.StartAsync(cancellationToken); -// } -// else if (_options.ParentProcessId == 0) -// { -// await base.OnRunAsync(cancellationToken); -// _waitInputTask = WaitInputAsync(); -// } -// else -// { -// await base.OnRunAsync(cancellationToken); -// } -// } - -// protected override async ValueTask OnDisposeAsync() -// { -// await base.OnDisposeAsync(); -// await _waitInputTask; -// } - -// private static bool GetStartupCondition(ApplicationOptions options) -// { -// if (options.SeedEndPoint is not null) -// { -// return false; -// } - -// return options.ParentProcessId == 0; -// } - -// private static string GetStartupMessage() -// { -// var startText = TerminalStringBuilder.GetString("start", TerminalColorType.Green); -// return $"\nType '{startText}' to start the node."; -// } - -// private async Task WaitInputAsync() -// { -// await Console.Out.WriteLineAsync("Press any key to exit."); -// await Task.Run(() => Console.ReadKey(intercept: true)); -// Cancel(); -// } -// } diff --git a/src/node/LibplanetConsole.Node.Executable/EntryCommands/RunCommand.cs b/src/node/LibplanetConsole.Node.Executable/EntryCommands/RunCommand.cs index e6433667..607f4f04 100644 --- a/src/node/LibplanetConsole.Node.Executable/EntryCommands/RunCommand.cs +++ b/src/node/LibplanetConsole.Node.Executable/EntryCommands/RunCommand.cs @@ -1,6 +1,5 @@ using JSSoft.Commands; using LibplanetConsole.Framework; -using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Node.Executable.EntryCommands; diff --git a/src/node/LibplanetConsole.Node.Executable/EntryCommands/StartCommand.cs b/src/node/LibplanetConsole.Node.Executable/EntryCommands/StartCommand.cs index 96de60e7..08d8fc4e 100644 --- a/src/node/LibplanetConsole.Node.Executable/EntryCommands/StartCommand.cs +++ b/src/node/LibplanetConsole.Node.Executable/EntryCommands/StartCommand.cs @@ -4,10 +4,8 @@ using LibplanetConsole.DataAnnotations; using LibplanetConsole.Framework; using LibplanetConsole.Node.Explorer; -using LibplanetConsole.Node.Services; using LibplanetConsole.Settings; using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Node.Executable.EntryCommands; @@ -59,7 +57,7 @@ protected override async Task OnExecuteAsync(CancellationToken cancellationToken { // Setup a HTTP/2 endpoint without TLS. options.ListenLocalhost(port, o => o.Protocols = HttpProtocols.Http2); - options.ListenLocalhost(port + 1, o => o.Protocols = HttpProtocols.Http1AndHttp2); + // options.ListenLocalhost(port + 1, o => o.Protocols = HttpProtocols.Http1AndHttp2); }); builder.Services.AddNode(applicationOptions); @@ -67,6 +65,7 @@ protected override async Task OnExecuteAsync(CancellationToken cancellationToken builder.Services.AddExplorer(builder.Configuration); builder.Services.AddGrpc(); + builder.Services.AddGrpcReflection(); using var app = builder.Build(); @@ -75,6 +74,7 @@ protected override async Task OnExecuteAsync(CancellationToken cancellationToken app.MapGet("/", () => "123"); app.UseAuthentication(); app.UseAuthorization(); + app.MapGrpcReflectionService().AllowAnonymous(); var @out = Console.Out; await @out.WriteLineAsync(); diff --git a/src/node/LibplanetConsole.Node.Executable/ServiceCollectionExtensions.cs b/src/node/LibplanetConsole.Node.Executable/ServiceCollectionExtensions.cs index 3ebe888d..8e5171cd 100644 --- a/src/node/LibplanetConsole.Node.Executable/ServiceCollectionExtensions.cs +++ b/src/node/LibplanetConsole.Node.Executable/ServiceCollectionExtensions.cs @@ -1,10 +1,8 @@ using JSSoft.Commands; -using LibplanetConsole.Framework; using LibplanetConsole.Logging; using LibplanetConsole.Node.Evidence; using LibplanetConsole.Node.Executable.Commands; using LibplanetConsole.Node.Executable.Tracers; -using LibplanetConsole.Node.Explorer; namespace LibplanetConsole.Node.Executable; diff --git a/src/node/LibplanetConsole.Node.Executable/TerminalHostedService.cs b/src/node/LibplanetConsole.Node.Executable/TerminalHostedService.cs index d26139f2..e70577dc 100644 --- a/src/node/LibplanetConsole.Node.Executable/TerminalHostedService.cs +++ b/src/node/LibplanetConsole.Node.Executable/TerminalHostedService.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using JSSoft.Commands.Extensions; using JSSoft.Terminals; using LibplanetConsole.Common.Extensions; @@ -8,15 +9,19 @@ internal sealed class TerminalHostedService( IHostApplicationLifetime applicationLifetime, CommandContext commandContext, SystemTerminal terminal, - ApplicationOptions options) : IHostedService, IDisposable + ApplicationOptions options, + ILogger logger) + : IHostedService, IDisposable { private readonly CancellationTokenSource _cancellationTokenSource = new(); private Task _runningTask = Task.CompletedTask; private Task _waitInputTask = Task.CompletedTask; + private Task _waitForExitTask = Task.CompletedTask; + private int _parentProcessId; public async Task StartAsync(CancellationToken cancellationToken) { - if (options.NoREPL != true) + if (options.NoREPL is false) { applicationLifetime.ApplicationStarted.Register(async () => { @@ -38,6 +43,12 @@ public async Task StartAsync(CancellationToken cancellationToken) _cancellationTokenSource.Cancel(); }); } + else if (options.ParentProcessId != 0 && + Process.GetProcessById(options.ParentProcessId) is { } parentProcess) + { + _parentProcessId = options.ParentProcessId; + _waitForExitTask = WaitForExit(parentProcess); + } else if (options.ParentProcessId == 0) { _waitInputTask = WaitInputAsync(); @@ -48,8 +59,7 @@ public async Task StartAsync(CancellationToken cancellationToken) public async Task StopAsync(CancellationToken cancellationToken) { - await _runningTask; - await _waitInputTask; + await Task.WhenAll(_runningTask, _waitInputTask, _waitForExitTask); await terminal.StopAsync(cancellationToken); } @@ -77,4 +87,11 @@ private async Task WaitInputAsync() await Task.Run(() => Console.ReadKey(intercept: true)); applicationLifetime.StopApplication(); } + + private async Task WaitForExit(Process process) + { + await process.WaitForExitAsync(); + logger.LogDebug("Parent process is exited: {ParentProcessId}.", _parentProcessId); + applicationLifetime.StopApplication(); + } } diff --git a/src/node/LibplanetConsole.Node.Executable/Tracers/BlockChainEventTracer.cs b/src/node/LibplanetConsole.Node.Executable/Tracers/BlockChainEventTracer.cs index c4ebaed4..a07e24ea 100644 --- a/src/node/LibplanetConsole.Node.Executable/Tracers/BlockChainEventTracer.cs +++ b/src/node/LibplanetConsole.Node.Executable/Tracers/BlockChainEventTracer.cs @@ -1,4 +1,5 @@ using JSSoft.Terminals; +using LibplanetConsole.Blockchain; using LibplanetConsole.Common.Extensions; namespace LibplanetConsole.Node.Executable.Tracers; diff --git a/src/node/LibplanetConsole.Node/ApplicationBase.cs b/src/node/LibplanetConsole.Node/ApplicationBase.cs deleted file mode 100644 index 3aa95a4a..00000000 --- a/src/node/LibplanetConsole.Node/ApplicationBase.cs +++ /dev/null @@ -1,83 +0,0 @@ -// using System.Diagnostics; -// using LibplanetConsole.Common.Extensions; -// using LibplanetConsole.Framework; -// using LibplanetConsole.Node.Services; -// using Microsoft.Extensions.DependencyInjection; -// using Microsoft.Extensions.Logging; - -// namespace LibplanetConsole.Node; - -// public abstract class ApplicationBase : ApplicationFramework, IApplication -// { -// private readonly IServiceProvider _serviceProvider; -// private readonly Node _node; -// private readonly Process? _parentProcess; -// private readonly ILogger _logger; -// private readonly ApplicationInfo _info; -// private readonly Task _waitForExitTask = Task.CompletedTask; -// // private NodeContext? _nodeContext; -// private Guid _closeToken; - -// protected ApplicationBase(IServiceProvider serviceProvider, ApplicationOptions options) -// : base(serviceProvider) -// { -// _serviceProvider = serviceProvider; -// _logger = serviceProvider.GetLogger(); -// _logger.LogDebug(Environment.CommandLine); -// _logger.LogDebug("Application initializing..."); -// _node = serviceProvider.GetRequiredService(); -// _info = new() -// { -// EndPoint = options.EndPoint, -// SeedEndPoint = options.SeedEndPoint, -// StorePath = options.StorePath, -// LogPath = options.LogPath, -// ParentProcessId = options.ParentProcessId, -// }; -// if (options.ParentProcessId != 0 && -// Process.GetProcessById(options.ParentProcessId) is { } parentProcess) -// { -// _parentProcess = parentProcess; -// _waitForExitTask = WaitForExit(parentProcess, Cancel); -// } - -// _logger.LogDebug("Application initialized."); -// } - -// public ApplicationInfo Info => _info; - -// protected override bool CanClose => _parentProcess?.HasExited == true; - -// public override object? GetService(Type serviceType) -// => _serviceProvider.GetService(serviceType); - -// protected override async Task OnRunAsync(CancellationToken cancellationToken) -// { -// _logger.LogDebug("NodeContext is starting: {EndPoint}", _info.EndPoint); -// // _nodeContext = _serviceProvider.GetRequiredService(); -// // _nodeContext.EndPoint = _info.EndPoint; -// // _closeToken = await _nodeContext.StartAsync(cancellationToken: default); -// _logger.LogDebug("NodeContext is started: {EndPoint}", _info.EndPoint); -// await base.OnRunAsync(cancellationToken); -// } - -// protected override async ValueTask OnDisposeAsync() -// { -// await base.OnDisposeAsync(); -// // if (_nodeContext is not null) -// // { -// // _logger.LogDebug("NodeContext is closing: {EndPoint}", _info.EndPoint); -// // await _nodeContext.CloseAsync(_closeToken, cancellationToken: default); -// // _nodeContext = null; -// // _logger.LogDebug("NodeContext is closed: {EndPoint}", _info.EndPoint); -// // } - -// await _waitForExitTask; -// } - -// private static async Task WaitForExit(Process process, Action cancelAction) -// { -// await process.WaitForExitAsync(); -// cancelAction.Invoke(); -// } -// } diff --git a/src/node/LibplanetConsole.Node/ApplicationInfoProvider.cs b/src/node/LibplanetConsole.Node/ApplicationInfoProvider.cs index aaf235ec..77c8e45b 100644 --- a/src/node/LibplanetConsole.Node/ApplicationInfoProvider.cs +++ b/src/node/LibplanetConsole.Node/ApplicationInfoProvider.cs @@ -3,8 +3,7 @@ namespace LibplanetConsole.Node; -internal sealed class ApplicationInfoProvider - : InfoProviderBase +internal sealed class ApplicationInfoProvider : InfoProviderBase { private readonly ApplicationInfo _info; diff --git a/src/node/LibplanetConsole.Node/BlockChainUtility.cs b/src/node/LibplanetConsole.Node/BlockChainUtility.cs index 4bba5468..f4710eca 100644 --- a/src/node/LibplanetConsole.Node/BlockChainUtility.cs +++ b/src/node/LibplanetConsole.Node/BlockChainUtility.cs @@ -4,6 +4,7 @@ using Libplanet.RocksDBStore; using Libplanet.Store; using Libplanet.Store.Trie; +using BlockChain = Libplanet.Blockchain.BlockChain; namespace LibplanetConsole.Node; diff --git a/src/node/LibplanetConsole.Node/BlockUtility.cs b/src/node/LibplanetConsole.Node/BlockUtility.cs index ff894645..2b3a5b73 100644 --- a/src/node/LibplanetConsole.Node/BlockUtility.cs +++ b/src/node/LibplanetConsole.Node/BlockUtility.cs @@ -1,6 +1,5 @@ using System.Collections.Immutable; using System.Security.Cryptography; -using LibplanetConsole.Common; namespace LibplanetConsole.Node; diff --git a/src/node/LibplanetConsole.Node/Commands/TxCommand.cs b/src/node/LibplanetConsole.Node/Commands/TxCommand.cs index d3b4a6a8..40bceff1 100644 --- a/src/node/LibplanetConsole.Node/Commands/TxCommand.cs +++ b/src/node/LibplanetConsole.Node/Commands/TxCommand.cs @@ -16,7 +16,7 @@ protected override async Task OnExecuteAsync(CancellationToken cancellationToken { Value = Text, }; - await blockChain.AddTransactionAsync([action], cancellationToken); + await blockChain.SendTransactionAsync([action], cancellationToken); await Out.WriteLineAsync($"{node.Address.ToShortString()}: {Text}"); } } diff --git a/src/node/LibplanetConsole.Node/Grpc/BlockChainGrpcServiceV1.cs b/src/node/LibplanetConsole.Node/Grpc/BlockChainGrpcServiceV1.cs new file mode 100644 index 00000000..7a5ec726 --- /dev/null +++ b/src/node/LibplanetConsole.Node/Grpc/BlockChainGrpcServiceV1.cs @@ -0,0 +1,97 @@ +using Grpc.Core; +using LibplanetConsole.Blockchain; +using LibplanetConsole.Blockchain.Grpc; +using LibplanetConsole.Grpc; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace LibplanetConsole.Node.Grpc; + +internal sealed class BlockChainGrpcServiceV1 : BlockChainGrpcService.BlockChainGrpcServiceBase +{ + private static readonly Codec _codec = new(); + private readonly Node _node; + private readonly IBlockChain _blockChain; + private readonly IHostApplicationLifetime _applicationLifetime; + private readonly ILogger _logger; + + public BlockChainGrpcServiceV1( + Node node, + IBlockChain blockChain, + IHostApplicationLifetime applicationLifetime, + ILogger logger) + { + _node = node; + _blockChain = blockChain; + _applicationLifetime = applicationLifetime; + _logger = logger; + _logger.LogDebug("BlockChainGrpcServiceV1 is created."); + } + + public async override Task SendTransaction( + SendTransactionRequest request, ServerCallContext context) + { + _logger.LogDebug("{MethodName} is called.", nameof(SendTransaction)); + var tx = Transaction.Deserialize(request.TransactionData.ToByteArray()); + await _node.SendTransactionAsync(tx, context.CancellationToken); + return new SendTransactionResponse { TxId = tx.Id.ToHex() }; + } + + public async override Task GetNextNonce( + GetNextNonceRequest request, ServerCallContext context) + { + var address = new Address(request.Address); + var nonce = await _blockChain.GetNextNonceAsync(address, context.CancellationToken); + return new GetNextNonceResponse { Nonce = nonce }; + } + + public override async Task GetTipHash( + GetTipHashRequest request, ServerCallContext context) + { + var blockHash = await _blockChain.GetTipHashAsync(context.CancellationToken); + return new GetTipHashResponse { BlockHash = blockHash.ToString() }; + } + + public override async Task GetState( + GetStateRequest request, ServerCallContext context) + { + BlockHash? blockHash = request.BlockHash == string.Empty + ? null : BlockHash.FromString(request.BlockHash); + var accountAddress = new Address(request.AccountAddress); + var address = new Address(request.Address); + var value = await _blockChain.GetStateAsync( + blockHash, accountAddress, address, context.CancellationToken); + var state = _codec.Encode(value); + return new GetStateResponse { StateData = Google.Protobuf.ByteString.CopyFrom(state) }; + } + + public override async Task GetBlockHash( + GetBlockHashRequest request, ServerCallContext context) + { + var height = request.Height; + var blockHash = await _blockChain.GetBlockHashAsync(height, context.CancellationToken); + return new GetBlockHashResponse { BlockHash = blockHash.ToString() }; + } + + public override async Task GetAction( + GetActionRequest request, ServerCallContext context) + { + var txId = TxId.FromHex(request.TxId); + var actionIndex = request.ActionIndex; + var action = await _node.GetActionAsync(txId, actionIndex, context.CancellationToken); + return new GetActionResponse { ActionData = Google.Protobuf.ByteString.CopyFrom(action) }; + } + + public override async Task GetBlockAppendedStream( + GetBlockAppendedStreamRequest request, + IServerStreamWriter responseStream, + ServerCallContext context) + { + var streamer = new EventStreamer( + responseStream, + attach: handler => _blockChain.BlockAppended += handler, + detach: handler => _blockChain.BlockAppended -= handler, + selector: e => new GetBlockAppendedStreamResponse { BlockInfo = e.BlockInfo }); + await streamer.RunAsync(_applicationLifetime, context.CancellationToken); + } +} diff --git a/src/node/LibplanetConsole.Node/Grpc/NodeGrpcServiceV1.cs b/src/node/LibplanetConsole.Node/Grpc/NodeGrpcServiceV1.cs new file mode 100644 index 00000000..59172fe0 --- /dev/null +++ b/src/node/LibplanetConsole.Node/Grpc/NodeGrpcServiceV1.cs @@ -0,0 +1,78 @@ +using Grpc.Core; +using LibplanetConsole.Common; +using LibplanetConsole.Grpc; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace LibplanetConsole.Node.Grpc; + +internal sealed class NodeGrpcServiceV1 : NodeGrpcService.NodeGrpcServiceBase +{ + private readonly IHostApplicationLifetime _applicationLifetime; + private readonly Node _node; + private readonly ILogger _logger; + + public NodeGrpcServiceV1( + IHostApplicationLifetime applicationLifetime, + Node node, + ILogger logger) + { + _applicationLifetime = applicationLifetime; + _node = node; + _logger = logger; + _logger.LogDebug("{GrpcServiceType} is created.", nameof(NodeGrpcServiceV1)); + } + + public override Task Ping(PingRequest request, ServerCallContext context) + { + return Task.FromResult(new PingResponse()); + } + + public override async Task Start(StartRequest request, ServerCallContext context) + { + _node.SeedEndPoint = EndPointUtility.Parse(request.SeedEndPoint); + await _node.StartAsync(context.CancellationToken); + return new StartResponse { NodeInfo = _node.Info }; + } + + public async override Task Stop(StopRequest request, ServerCallContext context) + { + await _node.StopAsync(context.CancellationToken); + return new StopResponse(); + } + + public override Task GetInfo(GetInfoRequest request, ServerCallContext context) + { + GetInfoResponse Action() => new() + { + NodeInfo = _node.Info, + }; + + return Task.Run(Action, context.CancellationToken); + } + + public override async Task GetStartedStream( + GetStartedStreamRequest request, + IServerStreamWriter responseStream, + ServerCallContext context) + { + var streamer = new EventStreamer( + responseStream, + handler => _node.Started += handler, + handler => _node.Started -= handler, + () => new GetStartedStreamResponse { NodeInfo = _node.Info }); + await streamer.RunAsync(_applicationLifetime, context.CancellationToken); + } + + public override async Task GetStoppedStream( + GetStoppedStreamRequest request, + IServerStreamWriter responseStream, + ServerCallContext context) + { + var streamer = new EventStreamer( + responseStream, + handler => _node.Stopped += handler, + handler => _node.Stopped -= handler); + await streamer.RunAsync(_applicationLifetime, context.CancellationToken); + } +} diff --git a/src/node/LibplanetConsole.Node/Grpc/SeedGrpcServiceV1.cs b/src/node/LibplanetConsole.Node/Grpc/SeedGrpcServiceV1.cs new file mode 100644 index 00000000..47d5c082 --- /dev/null +++ b/src/node/LibplanetConsole.Node/Grpc/SeedGrpcServiceV1.cs @@ -0,0 +1,20 @@ +using Grpc.Core; +using LibplanetConsole.Seed; +using LibplanetConsole.Seed.Grpc; + +namespace LibplanetConsole.Node.Grpc; + +public sealed class SeedGrpcServiceV1(ISeedService seedService) + : SeedGrpcService.SeedGrpcServiceBase +{ + public async override Task GetSeed( + GetSeedRequest request, ServerCallContext context) + { + var publicKey = new PrivateKey().PublicKey; + var seedInfo = await seedService.GetSeedAsync(publicKey, context.CancellationToken); + return new GetSeedResponse + { + SeedResult = seedInfo, + }; + } +} diff --git a/src/node/LibplanetConsole.Node/IBlockChain.cs b/src/node/LibplanetConsole.Node/IBlockChain.cs index ed07bb43..e410ad4d 100644 --- a/src/node/LibplanetConsole.Node/IBlockChain.cs +++ b/src/node/LibplanetConsole.Node/IBlockChain.cs @@ -1,4 +1,5 @@ using System.Security.Cryptography; +using LibplanetConsole.Blockchain; namespace LibplanetConsole.Node; @@ -6,7 +7,7 @@ public interface IBlockChain { event EventHandler? BlockAppended; - Task AddTransactionAsync(IAction[] actions, CancellationToken cancellationToken); + Task SendTransactionAsync(IAction[] actions, CancellationToken cancellationToken); Task GetNextNonceAsync(Address address, CancellationToken cancellationToken); diff --git a/src/node/LibplanetConsole.Node/INode.cs b/src/node/LibplanetConsole.Node/INode.cs index 72aefb88..836771c2 100644 --- a/src/node/LibplanetConsole.Node/INode.cs +++ b/src/node/LibplanetConsole.Node/INode.cs @@ -20,5 +20,5 @@ public interface INode : IVerifier, ISigner, IServiceProvider Task StopAsync(CancellationToken cancellationToken); - Task AddTransactionAsync(IAction[] actions, CancellationToken cancellationToken); + Task SendTransactionAsync(IAction[] actions, CancellationToken cancellationToken); } diff --git a/src/node/LibplanetConsole.Node/LibplanetConsole.Node.csproj b/src/node/LibplanetConsole.Node/LibplanetConsole.Node.csproj index 86f038fb..2f999b5f 100644 --- a/src/node/LibplanetConsole.Node/LibplanetConsole.Node.csproj +++ b/src/node/LibplanetConsole.Node/LibplanetConsole.Node.csproj @@ -23,8 +23,10 @@ + + diff --git a/src/node/LibplanetConsole.Node/Node.BlockChain.cs b/src/node/LibplanetConsole.Node/Node.BlockChain.cs index 9115fed9..890e0c13 100644 --- a/src/node/LibplanetConsole.Node/Node.BlockChain.cs +++ b/src/node/LibplanetConsole.Node/Node.BlockChain.cs @@ -10,13 +10,14 @@ internal sealed partial class Node : IBlockChain { private static readonly Codec _codec = new(); - public async Task AddTransactionAsync( + public async Task SendTransactionAsync( IAction[] actions, CancellationToken cancellationToken) { ObjectDisposedExceptionUtility.ThrowIf(_isDisposed, this); - InvalidOperationExceptionUtility.ThrowIf( - condition: IsRunning != true, - message: "Node is not running."); + if (IsRunning is false) + { + throw new InvalidOperationException("Node is not running."); + } var privateKey = PrivateKeyUtility.FromSecureString(_privateKey); var blockChain = BlockChain; @@ -28,17 +29,18 @@ public async Task AddTransactionAsync( privateKey: privateKey, genesisHash: genesisBlock.Hash, actions: new TxActionList(values)); - await AddTransactionAsync(transaction, cancellationToken); + await SendTransactionAsync(transaction, cancellationToken); return transaction.Id; } - public async Task AddTransactionAsync( + public async Task SendTransactionAsync( Transaction transaction, CancellationToken cancellationToken) { ObjectDisposedExceptionUtility.ThrowIf(_isDisposed, this); - InvalidOperationExceptionUtility.ThrowIf( - condition: IsRunning != true, - message: "Node is not running."); + if (IsRunning is false) + { + throw new InvalidOperationException("Node is not running."); + } _logger.LogDebug("Node adds a transaction: {TxId}", transaction.Id); var blockChain = BlockChain; @@ -72,9 +74,10 @@ public async Task AddTransactionAsync( public Task GetNextNonceAsync(Address address, CancellationToken cancellationToken) { ObjectDisposedExceptionUtility.ThrowIf(_isDisposed, this); - InvalidOperationExceptionUtility.ThrowIf( - condition: IsRunning != true, - message: "Node is not running."); + if (IsRunning is false) + { + throw new InvalidOperationException("Node is not running."); + } return Task.Run(GetNextNonce, cancellationToken); @@ -89,9 +92,10 @@ long GetNextNonce() public Task GetTipHashAsync(CancellationToken cancellationToken) { ObjectDisposedExceptionUtility.ThrowIf(_isDisposed, this); - InvalidOperationExceptionUtility.ThrowIf( - condition: IsRunning != true, - message: "Node is not running."); + if (IsRunning is false) + { + throw new InvalidOperationException("Node is not running."); + } BlockHash GetTipHash() { @@ -109,9 +113,10 @@ public Task GetStateAsync( CancellationToken cancellationToken) { ObjectDisposedExceptionUtility.ThrowIf(_isDisposed, this); - InvalidOperationExceptionUtility.ThrowIf( - condition: IsRunning != true, - message: "Node is not running."); + if (IsRunning is false) + { + throw new InvalidOperationException("Node is not running."); + } IValue GetStateByBlockHash() { @@ -136,9 +141,10 @@ public Task GetStateByStateRootHashAsync( CancellationToken cancellationToken) { ObjectDisposedExceptionUtility.ThrowIf(_isDisposed, this); - InvalidOperationExceptionUtility.ThrowIf( - condition: IsRunning != true, - message: "Node is not running."); + if (IsRunning is false) + { + throw new InvalidOperationException("Node is not running."); + } IValue GetStateByStateRootHash() { @@ -155,9 +161,10 @@ IValue GetStateByStateRootHash() public Task GetBlockHashAsync(long height, CancellationToken cancellationToken) { ObjectDisposedExceptionUtility.ThrowIf(_isDisposed, this); - InvalidOperationExceptionUtility.ThrowIf( - condition: IsRunning != true, - message: "Node is not running."); + if (IsRunning is false) + { + throw new InvalidOperationException("Node is not running."); + } BlockHash GetBlockHash() { diff --git a/src/node/LibplanetConsole.Node/Node.cs b/src/node/LibplanetConsole.Node/Node.cs index 2a19b582..27baa671 100644 --- a/src/node/LibplanetConsole.Node/Node.cs +++ b/src/node/LibplanetConsole.Node/Node.cs @@ -1,7 +1,6 @@ using System.Collections.Concurrent; using System.Security; using System.Security.Cryptography; -using Grpc.Core; using Grpc.Net.Client; using Libplanet.Blockchain; using Libplanet.Blockchain.Renderers; @@ -9,6 +8,7 @@ using Libplanet.Net.Consensus; using Libplanet.Net.Options; using Libplanet.Net.Transports; +using LibplanetConsole.Blockchain; using LibplanetConsole.Common; using LibplanetConsole.Common.Exceptions; using LibplanetConsole.Common.Extensions; @@ -17,7 +17,7 @@ namespace LibplanetConsole.Node; -internal sealed partial class Node : IActionRenderer, INode +internal sealed partial class Node : IActionRenderer, INode, IAsyncDisposable { private readonly SecureString _privateKey; private readonly string _storePath; @@ -139,14 +139,17 @@ public EndPoint SeedEndPoint public async Task StartAsync(CancellationToken cancellationToken) { ObjectDisposedExceptionUtility.ThrowIf(_isDisposed, this); - InvalidOperationExceptionUtility.ThrowIf(IsRunning == true, "Node is already running."); + if (IsRunning is true) + { + throw new InvalidOperationException("Node is already running."); + } if (_seedEndPoint is null) { throw new InvalidOperationException($"{nameof(SeedEndPoint)} is not initialized."); } - var seedInfo = await GetSeedInfoAsync(_seedEndPoint, cancellationToken); + var seedInfo = await GetSeedInfoAsync(_seedEndPoint, _logger, cancellationToken); var privateKey = PrivateKeyUtility.FromSecureString(_privateKey); var appProtocolVersion = _appProtocolVersion; var storePath = _storePath; @@ -204,9 +207,10 @@ var swarmTransport public async Task StopAsync(CancellationToken cancellationToken) { ObjectDisposedExceptionUtility.ThrowIf(_isDisposed, this); - InvalidOperationExceptionUtility.ThrowIf( - condition: IsRunning != true, - message: "Node is not running."); + if (IsRunning is false) + { + throw new InvalidOperationException("Node is not running."); + } if (_swarm is not null) { @@ -295,8 +299,9 @@ private static async Task CreateTransport( } private static async Task GetSeedInfoAsync( - EndPoint seedEndPoint, CancellationToken cancellationToken) + EndPoint seedEndPoint, ILogger logger, CancellationToken cancellationToken) { + logger.LogDebug("Getting seed info from {SeedEndPoint}", seedEndPoint); var address = $"http://{EndPointUtility.ToString(seedEndPoint)}"; var channelOptions = new GrpcChannelOptions { @@ -309,13 +314,14 @@ private static async Task GetSeedInfoAsync( }; var response = await client.GetSeedAsync(request, cancellationToken: cancellationToken); + logger.LogDebug("Got seed info from {SeedEndPoint}", seedEndPoint); return response.SeedResult; } private void UpdateNodeInfo() { var appProtocolVersion = _appProtocolVersion; - var nodeInfo = new NodeInfo + var nodeInfo = NodeInfo.Empty with { ProcessId = Environment.ProcessId, Address = Address, diff --git a/src/node/LibplanetConsole.Node/NodeEndpointRouteBuilderExtensions.cs b/src/node/LibplanetConsole.Node/NodeEndpointRouteBuilderExtensions.cs index 19306bb1..3d385827 100644 --- a/src/node/LibplanetConsole.Node/NodeEndpointRouteBuilderExtensions.cs +++ b/src/node/LibplanetConsole.Node/NodeEndpointRouteBuilderExtensions.cs @@ -1,4 +1,4 @@ -using LibplanetConsole.Node.Services; +using LibplanetConsole.Node.Grpc; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; diff --git a/src/node/LibplanetConsole.Node/NodeHostedService.cs b/src/node/LibplanetConsole.Node/NodeHostedService.cs index 026c69e3..00da0f80 100644 --- a/src/node/LibplanetConsole.Node/NodeHostedService.cs +++ b/src/node/LibplanetConsole.Node/NodeHostedService.cs @@ -1,25 +1,35 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using static LibplanetConsole.Common.EndPointUtility; namespace LibplanetConsole.Node; internal sealed class NodeHostedService( - IHostApplicationLifetime applicationLifetime, Node node, ILogger logger) + IHostApplicationLifetime applicationLifetime, + SeedService seedService, + Node node, + ApplicationOptions options, + ILogger logger) : IHostedService { + private CancellationTokenSource? _cancellationTokenSource; + public Task StartAsync(CancellationToken cancellationToken) { applicationLifetime.ApplicationStarted.Register(async () => { - logger.LogInformation("Application started."); - if (node.SeedEndPoint is { } seedEndPoint) + _cancellationTokenSource = new(); + try + { + await ExecuteAsync(_cancellationTokenSource.Token); + } + catch (Exception e) { - logger.LogDebug("Node auto-starting"); - node.SeedEndPoint = seedEndPoint; - await node.StartAsync(cancellationToken); - logger.LogDebug("Node auto-started"); + logger.LogError(e, "An error occurred while starting the node."); + applicationLifetime.StopApplication(); } }); + return Task.CompletedTask; } @@ -29,5 +39,28 @@ public async Task StopAsync(CancellationToken cancellationToken) { await node.StopAsync(cancellationToken); } + + await seedService.StopAsync(cancellationToken); + } + + private async Task ExecuteAsync(CancellationToken cancellationToken) + { + if (_cancellationTokenSource is not null) + { + await _cancellationTokenSource.CancelAsync(); + _cancellationTokenSource.Dispose(); + _cancellationTokenSource = null; + } + + if (CompareEndPoint(options.SeedEndPoint, options.EndPoint) is true) + { + await seedService.StartAsync(cancellationToken); + } + + if (options.SeedEndPoint is { } seedEndPoint) + { + node.SeedEndPoint = seedEndPoint; + await node.StartAsync(cancellationToken); + } } } diff --git a/src/node/LibplanetConsole.Node/SeedService.cs b/src/node/LibplanetConsole.Node/SeedService.cs index 8687d6d3..54a42f13 100644 --- a/src/node/LibplanetConsole.Node/SeedService.cs +++ b/src/node/LibplanetConsole.Node/SeedService.cs @@ -1,10 +1,9 @@ using LibplanetConsole.Seed; -using Microsoft.Extensions.Hosting; using static LibplanetConsole.Common.EndPointUtility; namespace LibplanetConsole.Node; -internal sealed class SeedService(ApplicationOptions options) : ISeedService, IHostedService +internal sealed class SeedService : ISeedService { private readonly PrivateKey _seedNodePrivateKey = new(); private SeedNode? _blocksyncSeedNode; @@ -29,26 +28,23 @@ public Task GetSeedAsync( return Task.Run(() => seedInfo, cancellationToken); } - async Task IHostedService.StartAsync(CancellationToken cancellationToken) + public async Task StartAsync(CancellationToken cancellationToken) { - if (CompareEndPoint(options.SeedEndPoint, options.EndPoint) is true) + _blocksyncSeedNode = new SeedNode(new() { - _blocksyncSeedNode = new SeedNode(new() - { - PrivateKey = _seedNodePrivateKey, - EndPoint = NextEndPoint(), - }); - _consensusSeedNode = new SeedNode(new() - { - PrivateKey = _seedNodePrivateKey, - EndPoint = NextEndPoint(), - }); - await _blocksyncSeedNode.StartAsync(cancellationToken); - await _consensusSeedNode.StartAsync(cancellationToken); - } + PrivateKey = _seedNodePrivateKey, + EndPoint = NextEndPoint(), + }); + _consensusSeedNode = new SeedNode(new() + { + PrivateKey = _seedNodePrivateKey, + EndPoint = NextEndPoint(), + }); + await _blocksyncSeedNode.StartAsync(cancellationToken); + await _consensusSeedNode.StartAsync(cancellationToken); } - async Task IHostedService.StopAsync(CancellationToken cancellationToken) + public async Task StopAsync(CancellationToken cancellationToken) { if (_blocksyncSeedNode is not null) { diff --git a/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs b/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs index e724470e..9583e68d 100644 --- a/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs +++ b/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs @@ -16,8 +16,7 @@ public static IServiceCollection AddNode( @this.AddSingleton(synchronizationContext); @this.AddSingleton(options); @this.AddSingleton() - .AddSingleton(s => s.GetRequiredService()) - .AddHostedService(s => s.GetRequiredService()); + .AddSingleton(s => s.GetRequiredService()); @this.AddSingleton() .AddSingleton(s => s.GetRequiredService()) .AddSingleton(s => s.GetRequiredService()); diff --git a/src/shared/LibplanetConsole.Blockchain/BlockEventArgs.cs b/src/shared/LibplanetConsole.Blockchain/BlockEventArgs.cs new file mode 100644 index 00000000..4f5c73b4 --- /dev/null +++ b/src/shared/LibplanetConsole.Blockchain/BlockEventArgs.cs @@ -0,0 +1,6 @@ +namespace LibplanetConsole.Blockchain; + +public sealed class BlockEventArgs(BlockInfo blockInfo) : EventArgs +{ + public BlockInfo BlockInfo { get; } = blockInfo; +} diff --git a/src/shared/LibplanetConsole.Node/BlockInfo.Node.cs b/src/shared/LibplanetConsole.Blockchain/BlockInfo.Node.cs similarity index 55% rename from src/shared/LibplanetConsole.Node/BlockInfo.Node.cs rename to src/shared/LibplanetConsole.Blockchain/BlockInfo.Node.cs index 317d6ec0..3da91018 100644 --- a/src/shared/LibplanetConsole.Node/BlockInfo.Node.cs +++ b/src/shared/LibplanetConsole.Blockchain/BlockInfo.Node.cs @@ -4,7 +4,7 @@ using Libplanet.Types.Tx; using LibplanetConsole.Common; -namespace LibplanetConsole.Node; +namespace LibplanetConsole.Blockchain; public readonly partial record struct BlockInfo { @@ -13,12 +13,6 @@ public BlockInfo(BlockChain blockChain, Block block) Height = block.Index; Hash = block.Hash; Miner = block.Miner; - - TransactionInfo GetTransaction(Transaction transaction) - { - var execution = blockChain.GetTxExecution(block.Hash, transaction.Id); - return new TransactionInfo(execution, transaction) { Height = block.Index }; - } } } #endif // LIBPLANET_NODE diff --git a/src/shared/LibplanetConsole.Node/BlockInfo.cs b/src/shared/LibplanetConsole.Blockchain/BlockInfo.cs similarity index 68% rename from src/shared/LibplanetConsole.Node/BlockInfo.cs rename to src/shared/LibplanetConsole.Blockchain/BlockInfo.cs index 8718dd33..52af8475 100644 --- a/src/shared/LibplanetConsole.Node/BlockInfo.cs +++ b/src/shared/LibplanetConsole.Blockchain/BlockInfo.cs @@ -1,6 +1,6 @@ -using GrpcBlockInfo = LibplanetConsole.Node.Grpc.BlockInfo; +using LibplanetConsole.Blockchain.Grpc; -namespace LibplanetConsole.Node; +namespace LibplanetConsole.Blockchain; public readonly partial record struct BlockInfo { @@ -14,7 +14,7 @@ public BlockInfo() public Address Miner { get; init; } - public static implicit operator BlockInfo(GrpcBlockInfo blockInfo) + public static implicit operator BlockInfo(BlockInformation blockInfo) { return new BlockInfo { @@ -24,9 +24,9 @@ public static implicit operator BlockInfo(GrpcBlockInfo blockInfo) }; } - public static implicit operator GrpcBlockInfo(BlockInfo blockInfo) + public static implicit operator BlockInformation(BlockInfo blockInfo) { - return new GrpcBlockInfo + return new BlockInformation { Height = blockInfo.Height, Hash = blockInfo.Hash.ToString(), diff --git a/src/client/LibplanetConsole.Client/Grpc/BlockChainService.cs b/src/shared/LibplanetConsole.Blockchain/Grpc/BlockChainService.cs similarity index 67% rename from src/client/LibplanetConsole.Client/Grpc/BlockChainService.cs rename to src/shared/LibplanetConsole.Blockchain/Grpc/BlockChainService.cs index 7ee48c15..46124843 100644 --- a/src/client/LibplanetConsole.Client/Grpc/BlockChainService.cs +++ b/src/shared/LibplanetConsole.Blockchain/Grpc/BlockChainService.cs @@ -1,15 +1,19 @@ +#if LIBPLANET_CONSOLE || LIBPLANET_CLIENT using Grpc.Net.Client; +using LibplanetConsole.Grpc; using LibplanetConsole.Node; +using LibplanetConsole.Node.Grpc; +using static LibplanetConsole.Blockchain.Grpc.BlockChainGrpcService; -namespace LibplanetConsole.Client.Grpc; +namespace LibplanetConsole.Blockchain.Grpc; internal sealed class BlockChainService(GrpcChannel channel) - : Node.Grpc.BlockChainGrpcService.BlockChainGrpcServiceClient(channel), IDisposable + : BlockChainGrpcServiceClient(channel), IDisposable { - private StreamReceiver? _blockAppendedReceiver; + private StreamReceiver? _blockAppendedReceiver; private bool _isDisposed; - public event EventHandler? BlockAppended; + public event EventHandler? BlockAppended; public void Dispose() { @@ -30,7 +34,7 @@ public async Task StartAsync(CancellationToken cancellationToken) _blockAppendedReceiver = new( GetBlockAppendedStream(new(), cancellationToken: cancellationToken), - (response) => BlockAppended?.Invoke(this, response.BlockInfo)); + (response) => BlockAppended?.Invoke(this, new(response.BlockInfo))); await _blockAppendedReceiver.StartAsync(cancellationToken); } @@ -45,3 +49,4 @@ public async Task StopAsync(CancellationToken cancellationToken) _blockAppendedReceiver = null; } } +#endif // LIBPLANET_CONSOLE || LIBPLANET_CLIENT diff --git a/src/shared/LibplanetConsole.Node/Protos/BlockChainGrpcService.proto b/src/shared/LibplanetConsole.Blockchain/Protos/BlockChainGrpcService.proto similarity index 84% rename from src/shared/LibplanetConsole.Node/Protos/BlockChainGrpcService.proto rename to src/shared/LibplanetConsole.Blockchain/Protos/BlockChainGrpcService.proto index d0453f9f..56b38764 100644 --- a/src/shared/LibplanetConsole.Node/Protos/BlockChainGrpcService.proto +++ b/src/shared/LibplanetConsole.Blockchain/Protos/BlockChainGrpcService.proto @@ -1,11 +1,10 @@ syntax = "proto3"; -option csharp_namespace = "LibplanetConsole.Node.Grpc"; +option csharp_namespace = "LibplanetConsole.Blockchain.Grpc"; -package libplanet.console.node.v1; +package libplanet.console.blockchain.v1; service BlockChainGrpcService { - rpc IsReady(IsReadyRequest) returns (IsReadyResponse); rpc SendTransaction(SendTransactionRequest) returns (SendTransactionResponse); rpc GetNextNonce(GetNextNonceRequest) returns (GetNextNonceResponse); rpc GetTipHash(GetTipHashRequest) returns (GetTipHashResponse); @@ -16,19 +15,12 @@ service BlockChainGrpcService { rpc GetBlockAppendedStream(GetBlockAppendedStreamRequest) returns (stream GetBlockAppendedStreamResponse); } -message BlockInfo { +message BlockInformation { int64 height = 1; string hash = 2; string miner = 3; } -message IsReadyRequest { -} - -message IsReadyResponse { - bool is_ready = 1; -} - message SendTransactionRequest { bytes transaction_data = 1; } @@ -86,5 +78,5 @@ message GetBlockAppendedStreamRequest { } message GetBlockAppendedStreamResponse { - BlockInfo block_info = 1; + BlockInformation block_info = 1; } diff --git a/src/shared/LibplanetConsole.Client/ClientEventArgs.cs b/src/shared/LibplanetConsole.Client/ClientEventArgs.cs new file mode 100644 index 00000000..1a8ee907 --- /dev/null +++ b/src/shared/LibplanetConsole.Client/ClientEventArgs.cs @@ -0,0 +1,6 @@ +namespace LibplanetConsole.Client; + +public sealed class ClientEventArgs(ClientInfo clientInfo) : EventArgs +{ + public ClientInfo ClientInfo { get; } = clientInfo; +} diff --git a/src/shared/LibplanetConsole.Client/ClientInfo.cs b/src/shared/LibplanetConsole.Client/ClientInfo.cs index e60235dc..9a3a8ca3 100644 --- a/src/shared/LibplanetConsole.Client/ClientInfo.cs +++ b/src/shared/LibplanetConsole.Client/ClientInfo.cs @@ -1,3 +1,5 @@ +using LibplanetConsole.Client.Grpc; + namespace LibplanetConsole.Client; public readonly record struct ClientInfo @@ -8,5 +10,35 @@ public readonly record struct ClientInfo public BlockHash GenesisHash { get; init; } + public BlockHash TipHash { get; init; } + public bool IsRunning { get; init; } + + public static ClientInfo Empty { get; } = new ClientInfo + { + }; + + public static implicit operator ClientInfo(ClientInformation clientInfo) + { + return new ClientInfo + { + Address = new Address(clientInfo.Address), + NodeAddress = new Address(clientInfo.NodeAddress), + GenesisHash = BlockHash.FromString(clientInfo.GenesisHash), + TipHash = BlockHash.FromString(clientInfo.TipHash), + IsRunning = clientInfo.IsRunning, + }; + } + + public static implicit operator ClientInformation(ClientInfo clientInfo) + { + return new ClientInformation + { + Address = clientInfo.Address.ToHex(), + NodeAddress = clientInfo.NodeAddress.ToHex(), + GenesisHash = clientInfo.GenesisHash.ToString(), + TipHash = clientInfo.TipHash.ToString(), + IsRunning = clientInfo.IsRunning, + }; + } } diff --git a/src/shared/LibplanetConsole.Client/Grpc/ClientService.cs b/src/shared/LibplanetConsole.Client/Grpc/ClientService.cs new file mode 100644 index 00000000..bb831ae3 --- /dev/null +++ b/src/shared/LibplanetConsole.Client/Grpc/ClientService.cs @@ -0,0 +1,103 @@ +#if LIBPLANET_CONSOLE +using Grpc.Net.Client; +using LibplanetConsole.Client; +using LibplanetConsole.Client.Grpc; +using LibplanetConsole.Grpc; +using static LibplanetConsole.Client.Grpc.ClientGrpcService; + +namespace LibplanetConsole.Client.Grpc; + +internal sealed class ClientService(GrpcChannel channel) + : ClientGrpcServiceClient(channel), IDisposable +{ + private ConnectionMonitor? _connection; + private StreamReceiver? _startedReceiver; + private StreamReceiver? _stoppedReceiver; + private bool _isDisposed; + + public event EventHandler? Disconnected; + + public event EventHandler? Started; + + public event EventHandler? Stopped; + + public void Dispose() + { + if (_isDisposed is false) + { + _startedReceiver?.Dispose(); + _startedReceiver = null; + _stoppedReceiver?.Dispose(); + _stoppedReceiver = null; + _connection?.Dispose(); + _connection = null; + _isDisposed = true; + } + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + if (_connection is not null) + { + throw new InvalidOperationException($"{nameof(ClientService)} is already started."); + } + + _connection = new ConnectionMonitor(this, CheckConnectionAsync); + _connection.Disconnected += Connection_Disconnected; + await _connection.StartAsync(cancellationToken); + _startedReceiver = new( + GetStartedStream(new(), cancellationToken: cancellationToken), + (response) => Started?.Invoke(this, new(response.ClientInfo))); + _stoppedReceiver = new( + GetStoppedStream(new(), cancellationToken: cancellationToken), + (response) => Stopped?.Invoke(this, EventArgs.Empty)); + await Task.WhenAll( + _startedReceiver.StartAsync(cancellationToken), + _stoppedReceiver.StartAsync(cancellationToken)); + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + if (_connection is null) + { + throw new InvalidOperationException($"{nameof(ClientService)} is not started."); + } + + if (_startedReceiver is not null) + { + await _startedReceiver.StopAsync(cancellationToken); + _startedReceiver = null; + } + + if (_stoppedReceiver is not null) + { + await _stoppedReceiver.StopAsync(cancellationToken); + _stoppedReceiver = null; + } + + _connection.Disconnected -= Connection_Disconnected; + await _connection.StopAsync(cancellationToken); + _connection = null; + } + + private static async Task CheckConnectionAsync( + ClientService clientService, CancellationToken cancellationToken) + { + await clientService.PingAsync(new(), cancellationToken: cancellationToken); + } + + private void Connection_Disconnected(object? sender, EventArgs e) + { + if (sender is ConnectionMonitor connection && connection == _connection) + { + _startedReceiver?.Dispose(); + _startedReceiver = null; + _stoppedReceiver?.Dispose(); + _stoppedReceiver = null; + _connection.Dispose(); + _connection = null; + Disconnected?.Invoke(this, e); + } + } +} +#endif // LIBPLANET_CONSOLE || LIBPLANET_CLIENT diff --git a/src/shared/LibplanetConsole.Client/Protos/ClientGrpcService.proto b/src/shared/LibplanetConsole.Client/Protos/ClientGrpcService.proto new file mode 100644 index 00000000..2adadc1b --- /dev/null +++ b/src/shared/LibplanetConsole.Client/Protos/ClientGrpcService.proto @@ -0,0 +1,63 @@ +syntax = "proto3"; + +option csharp_namespace = "LibplanetConsole.Client.Grpc"; + +package libplanet.console.client.v1; + +service ClientGrpcService { + rpc Ping(PingRequest) returns (PingResponse); + rpc Start(StartRequest) returns (StartResponse); + rpc Stop(StopRequest) returns (StopResponse); + rpc GetInfo(GetInfoRequest) returns (GetInfoResponse); + + rpc GetStartedStream(GetStartedStreamRequest) returns (stream GetStartedStreamResponse); + rpc GetStoppedStream(GetStoppedStreamRequest) returns (stream GetStoppedStreamResponse); +} + +message ClientInformation { + string address = 1; + string node_address = 2; + string genesis_hash = 3; + string tip_hash = 4; + bool is_running = 5; +} + +message PingRequest { +} + +message PingResponse { +} + +message StartRequest { + string node_end_point = 1; +} + +message StartResponse { + ClientInformation client_info = 1; +} + +message StopRequest { +} + +message StopResponse { +} + +message GetInfoRequest { +} + +message GetInfoResponse { + ClientInformation client_info = 1; +} + +message GetStartedStreamRequest { +} + +message GetStartedStreamResponse { + ClientInformation client_info = 1; +} + +message GetStoppedStreamRequest { +} + +message GetStoppedStreamResponse { +} diff --git a/src/shared/LibplanetConsole.Client/Services/IClientCallback.cs b/src/shared/LibplanetConsole.Client/Services/IClientCallback.cs deleted file mode 100644 index 504f2c19..00000000 --- a/src/shared/LibplanetConsole.Client/Services/IClientCallback.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace LibplanetConsole.Client.Services; - -public interface IClientCallback -{ - void OnStarted(ClientInfo clientInfo); - - void OnStopped(); -} diff --git a/src/shared/LibplanetConsole.Client/Services/IClientService.cs b/src/shared/LibplanetConsole.Client/Services/IClientService.cs deleted file mode 100644 index 9530cbd4..00000000 --- a/src/shared/LibplanetConsole.Client/Services/IClientService.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace LibplanetConsole.Client.Services; - -public interface IClientService -{ - Task StartAsync(string nodeEndPoint, CancellationToken cancellationToken); - - Task StopAsync(CancellationToken cancellationToken); - - Task GetInfoAsync(CancellationToken cancellationToken); - - Task SendTransactionAsync( - TransactionOptions transactionOptions, CancellationToken cancellationToken); -} diff --git a/src/shared/LibplanetConsole.Client/Services/TransactionOptions.cs b/src/shared/LibplanetConsole.Client/Services/TransactionOptions.cs deleted file mode 100644 index 7b732e89..00000000 --- a/src/shared/LibplanetConsole.Client/Services/TransactionOptions.cs +++ /dev/null @@ -1,8 +0,0 @@ -using LibplanetConsole.Common; - -namespace LibplanetConsole.Client.Services; - -public sealed record class TransactionOptions : OptionsBase -{ - public required string Text { get; init; } = string.Empty; -} diff --git a/src/shared/LibplanetConsole.Grpc/ConnectionMonitor.cs b/src/shared/LibplanetConsole.Grpc/ConnectionMonitor.cs index c092b97e..f45c90a5 100644 --- a/src/shared/LibplanetConsole.Grpc/ConnectionMonitor.cs +++ b/src/shared/LibplanetConsole.Grpc/ConnectionMonitor.cs @@ -1,15 +1,8 @@ +#if LIBPLANET_CONSOLE || LIBPLANET_CLIENT using Grpc.Core; using LibplanetConsole.Common.Threading; -#if LIBPLANET_CONSOLE -namespace LibplanetConsole.Console.Grpc; -#elif LIBPLANET_NODE -namespace LibplanetConsole.Node.Grpc; -#elif LIBPLANET_CLIENT -namespace LibplanetConsole.Client.Grpc; -#else -#error "Either LIBPLANET_CONSOLE or LIBPLANET_NODE must be defined." -#endif +namespace LibplanetConsole.Grpc; internal class ConnectionMonitor(T client, Func action) : RunTask @@ -18,15 +11,20 @@ internal class ConnectionMonitor(T client, Func a public TimeSpan Interval { get; set; } = TimeSpan.FromSeconds(5); + protected override async Task OnStartAsync(CancellationToken cancellationToken) + { + await action(client, cancellationToken); + } + protected override async Task OnRunAsync(CancellationToken cancellationToken) { - while (await TaskUtility.TryDelay(1, cancellationToken)) + while (await TaskUtility.TryDelay(Interval, cancellationToken)) { try { await action(client, cancellationToken); } - catch (RpcException) + catch (RpcException e) { Disconnected?.Invoke(this, EventArgs.Empty); break; @@ -34,3 +32,4 @@ protected override async Task OnRunAsync(CancellationToken cancellationToken) } } } +#endif // LIBPLANET_CONSOLE || LIBPLANET_CLIENT diff --git a/src/shared/LibplanetConsole.Grpc/EventStreamer.cs b/src/shared/LibplanetConsole.Grpc/EventStreamer.cs index 578fbcce..81ba1926 100644 --- a/src/shared/LibplanetConsole.Grpc/EventStreamer.cs +++ b/src/shared/LibplanetConsole.Grpc/EventStreamer.cs @@ -1,15 +1,7 @@ #pragma warning disable SA1402 // File may only contain a single type using Grpc.Core; -#if LIBPLANET_CONSOLE -namespace LibplanetConsole.Console.Grpc; -#elif LIBPLANET_NODE -namespace LibplanetConsole.Node.Grpc; -#elif LIBPLANET_CLIENT -namespace LibplanetConsole.Client.Grpc; -#else -#error "Either LIBPLANET_CONSOLE or LIBPLANET_NODE must be defined." -#endif +namespace LibplanetConsole.Grpc; internal sealed class EventStreamer( IAsyncStreamWriter streamWriter, diff --git a/src/shared/LibplanetConsole.Grpc/RunTask.cs b/src/shared/LibplanetConsole.Grpc/RunTask.cs index 520c386b..8016e2ac 100644 --- a/src/shared/LibplanetConsole.Grpc/RunTask.cs +++ b/src/shared/LibplanetConsole.Grpc/RunTask.cs @@ -1,12 +1,4 @@ -#if LIBPLANET_CONSOLE -namespace LibplanetConsole.Console.Grpc; -#elif LIBPLANET_NODE -namespace LibplanetConsole.Node.Grpc; -#elif LIBPLANET_CLIENT -namespace LibplanetConsole.Client.Grpc; -#else -#error "Either LIBPLANET_CONSOLE or LIBPLANET_NODE must be defined." -#endif +namespace LibplanetConsole.Grpc; internal abstract class RunTask : IDisposable { @@ -27,22 +19,20 @@ public async Task StartAsync(CancellationToken cancellationToken) { if (_isRunning is true) { - throw new InvalidOperationException( - $"{GetType().Name} is already running."); + throw new InvalidOperationException($"{GetType().Name} is already running."); } + await OnStartAsync(cancellationToken); _cancellationTokenSource = new(); _runningTask = OnRunAsync(_cancellationTokenSource.Token); _isRunning = true; - await Task.CompletedTask; } public async Task StopAsync(CancellationToken cancellationToken) { if (_isRunning is false) { - throw new InvalidOperationException( - $"{GetType().Name} is not running."); + throw new InvalidOperationException($"{GetType().Name} is not running."); } if (_cancellationTokenSource is not null) @@ -53,6 +43,7 @@ public async Task StopAsync(CancellationToken cancellationToken) } await _runningTask; + await OnStopAsync(cancellationToken); _isRunning = false; } @@ -67,6 +58,10 @@ public async Task RunAsync(CancellationToken cancellationToken) protected abstract Task OnRunAsync(CancellationToken cancellationToken); + protected virtual Task OnStartAsync(CancellationToken cancellationToken) => Task.CompletedTask; + + protected virtual Task OnStopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + protected virtual void Dispose(bool disposing) { if (!_disposedValue) diff --git a/src/shared/LibplanetConsole.Grpc/StreamReceiver.cs b/src/shared/LibplanetConsole.Grpc/StreamReceiver.cs index 15900669..caa8667b 100644 --- a/src/shared/LibplanetConsole.Grpc/StreamReceiver.cs +++ b/src/shared/LibplanetConsole.Grpc/StreamReceiver.cs @@ -1,15 +1,7 @@ #pragma warning disable SA1402 // File may only contain a single type using Grpc.Core; -#if LIBPLANET_CONSOLE -namespace LibplanetConsole.Console.Grpc; -#elif LIBPLANET_NODE -namespace LibplanetConsole.Node.Grpc; -#elif LIBPLANET_CLIENT -namespace LibplanetConsole.Client.Grpc; -#else -#error "Either LIBPLANET_CONSOLE or LIBPLANET_NODE must be defined." -#endif +namespace LibplanetConsole.Grpc; internal sealed class StreamReceiver( AsyncServerStreamingCall streamingCall, diff --git a/src/shared/LibplanetConsole.Grpc/Streamer.cs b/src/shared/LibplanetConsole.Grpc/Streamer.cs index 8fec405a..319f7bd8 100644 --- a/src/shared/LibplanetConsole.Grpc/Streamer.cs +++ b/src/shared/LibplanetConsole.Grpc/Streamer.cs @@ -1,15 +1,7 @@ using Grpc.Core; using Microsoft.Extensions.Hosting; -#if LIBPLANET_CONSOLE -namespace LibplanetConsole.Console.Grpc; -#elif LIBPLANET_NODE -namespace LibplanetConsole.Node.Grpc; -#elif LIBPLANET_CLIENT -namespace LibplanetConsole.Client.Grpc; -#else -#error "Either LIBPLANET_CONSOLE or LIBPLANET_NODE must be defined." -#endif +namespace LibplanetConsole.Grpc; internal abstract class Streamer(IAsyncStreamWriter streamWriter) { diff --git a/src/shared/LibplanetConsole.Logging/LoggingExtensions.cs b/src/shared/LibplanetConsole.Logging/LoggingExtensions.cs index 43c7d2b3..842190fd 100644 --- a/src/shared/LibplanetConsole.Logging/LoggingExtensions.cs +++ b/src/shared/LibplanetConsole.Logging/LoggingExtensions.cs @@ -1,5 +1,3 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using Serilog; using Serilog.Events; diff --git a/src/shared/LibplanetConsole.Node/BlockEventArgs.cs b/src/shared/LibplanetConsole.Node/BlockEventArgs.cs deleted file mode 100644 index 851aa56d..00000000 --- a/src/shared/LibplanetConsole.Node/BlockEventArgs.cs +++ /dev/null @@ -1,16 +0,0 @@ -using LibplanetConsole.Node; - -#if LIBPLANET_CONSOLE -namespace LibplanetConsole.Console; -#elif LIBPLANET_NODE -namespace LibplanetConsole.Node; -#elif LIBPLANET_CLIENT -namespace LibplanetConsole.Client; -#else -#error "Either LIBPLANET_CONSOLE or LIBPLANET_NODE must be defined." -#endif - -public sealed class BlockEventArgs(BlockInfo blockInfo) : EventArgs -{ - public BlockInfo BlockInfo { get; } = blockInfo; -} diff --git a/src/shared/LibplanetConsole.Node/GenesisOptions.cs b/src/shared/LibplanetConsole.Node/GenesisOptions.cs index 722f1169..94465ce6 100644 --- a/src/shared/LibplanetConsole.Node/GenesisOptions.cs +++ b/src/shared/LibplanetConsole.Node/GenesisOptions.cs @@ -1,4 +1,4 @@ -namespace LibplanetConsole.Common; +namespace LibplanetConsole.Node; public sealed record class GenesisOptions { diff --git a/src/client/LibplanetConsole.Client/Grpc/NodeService.cs b/src/shared/LibplanetConsole.Node/Grpc/NodeService.cs similarity index 70% rename from src/client/LibplanetConsole.Client/Grpc/NodeService.cs rename to src/shared/LibplanetConsole.Node/Grpc/NodeService.cs index fe2234e5..858c00b5 100644 --- a/src/client/LibplanetConsole.Client/Grpc/NodeService.cs +++ b/src/shared/LibplanetConsole.Node/Grpc/NodeService.cs @@ -1,19 +1,22 @@ +#if LIBPLANET_CONSOLE || LIBPLANET_CLIENT +using Grpc.Core; using Grpc.Net.Client; -using LibplanetConsole.Node; +using LibplanetConsole.Grpc; +using static LibplanetConsole.Node.Grpc.NodeGrpcService; -namespace LibplanetConsole.Client.Grpc; +namespace LibplanetConsole.Node.Grpc; internal sealed class NodeService(GrpcChannel channel) - : Node.Grpc.NodeGrpcService.NodeGrpcServiceClient(channel), IDisposable + : NodeGrpcServiceClient(channel), IDisposable { - private Connection? _connection; - private StreamReceiver? _startedReceiver; - private StreamReceiver? _stoppedReceiver; + private ConnectionMonitor? _connection; + private StreamReceiver? _startedReceiver; + private StreamReceiver? _stoppedReceiver; private bool _isDisposed; public event EventHandler? Disconnected; - public event EventHandler? Started; + public event EventHandler? Started; public event EventHandler? Stopped; @@ -38,12 +41,12 @@ public async Task StartAsync(CancellationToken cancellationToken) throw new InvalidOperationException($"{nameof(NodeService)} is already started."); } - _connection = new Connection(this); + _connection = new ConnectionMonitor(this, CheckConnectionAsync); _connection.Disconnected += Connection_Disconnected; await _connection.StartAsync(cancellationToken); _startedReceiver = new( GetStartedStream(new(), cancellationToken: cancellationToken), - (response) => Started?.Invoke(this, response.NodeInfo)); + (response) => Started?.Invoke(this, new(response.NodeInfo))); _stoppedReceiver = new( GetStoppedStream(new(), cancellationToken: cancellationToken), (response) => Stopped?.Invoke(this, EventArgs.Empty)); @@ -76,9 +79,15 @@ public async Task StopAsync(CancellationToken cancellationToken) _connection = null; } + private static async Task CheckConnectionAsync( + NodeService nodeService, CancellationToken cancellationToken) + { + await nodeService.PingAsync(new(), cancellationToken: cancellationToken); + } + private void Connection_Disconnected(object? sender, EventArgs e) { - if (sender is Connection connection && connection == _connection) + if (sender is ConnectionMonitor connection && connection == _connection) { _startedReceiver?.Dispose(); _startedReceiver = null; @@ -90,3 +99,4 @@ private void Connection_Disconnected(object? sender, EventArgs e) } } } +#endif // LIBPLANET_CONSOLE || LIBPLANET_CLIENT diff --git a/src/shared/LibplanetConsole.Node/NodeEventArgs.cs b/src/shared/LibplanetConsole.Node/NodeEventArgs.cs new file mode 100644 index 00000000..4da4cc32 --- /dev/null +++ b/src/shared/LibplanetConsole.Node/NodeEventArgs.cs @@ -0,0 +1,6 @@ +namespace LibplanetConsole.Node; + +public sealed class NodeEventArgs(NodeInfo nodeInfo) : EventArgs +{ + public NodeInfo NodeInfo { get; } = nodeInfo; +} diff --git a/src/shared/LibplanetConsole.Node/NodeInfo.cs b/src/shared/LibplanetConsole.Node/NodeInfo.cs index 0e88d2ad..d13a56c1 100644 --- a/src/shared/LibplanetConsole.Node/NodeInfo.cs +++ b/src/shared/LibplanetConsole.Node/NodeInfo.cs @@ -1,4 +1,4 @@ -using GrpcNodeInfo = LibplanetConsole.Node.Grpc.NodeInfo; +using LibplanetConsole.Node.Grpc; namespace LibplanetConsole.Node; @@ -28,32 +28,32 @@ public readonly record struct NodeInfo ConsensusEndPoint = string.Empty, }; - public static implicit operator GrpcNodeInfo(NodeInfo nodeInfo) + public static implicit operator NodeInfo(NodeInformation nodeInfo) { - return new GrpcNodeInfo + return new NodeInfo { ProcessId = nodeInfo.ProcessId, AppProtocolVersion = nodeInfo.AppProtocolVersion, SwarmEndPoint = nodeInfo.SwarmEndPoint, ConsensusEndPoint = nodeInfo.ConsensusEndPoint, - Address = nodeInfo.Address.ToHex(), - GenesisHash = nodeInfo.GenesisHash.ToString(), - TipHash = nodeInfo.TipHash.ToString(), + Address = new Address(nodeInfo.Address), + GenesisHash = BlockHash.FromString(nodeInfo.GenesisHash), + TipHash = BlockHash.FromString(nodeInfo.TipHash), IsRunning = nodeInfo.IsRunning, }; } - public static implicit operator NodeInfo(GrpcNodeInfo nodeInfo) + public static implicit operator NodeInformation(NodeInfo nodeInfo) { - return new NodeInfo + return new NodeInformation { ProcessId = nodeInfo.ProcessId, AppProtocolVersion = nodeInfo.AppProtocolVersion, SwarmEndPoint = nodeInfo.SwarmEndPoint, ConsensusEndPoint = nodeInfo.ConsensusEndPoint, - Address = new Address(nodeInfo.Address), - GenesisHash = BlockHash.FromString(nodeInfo.GenesisHash), - TipHash = BlockHash.FromString(nodeInfo.TipHash), + Address = nodeInfo.Address.ToHex(), + GenesisHash = nodeInfo.GenesisHash.ToString(), + TipHash = nodeInfo.TipHash.ToString(), IsRunning = nodeInfo.IsRunning, }; } diff --git a/src/shared/LibplanetConsole.Node/Protos/NodeGrpcService.proto b/src/shared/LibplanetConsole.Node/Protos/NodeGrpcService.proto index 7299f3c4..eacdd1bc 100644 --- a/src/shared/LibplanetConsole.Node/Protos/NodeGrpcService.proto +++ b/src/shared/LibplanetConsole.Node/Protos/NodeGrpcService.proto @@ -14,7 +14,7 @@ service NodeGrpcService { rpc GetStoppedStream(GetStoppedStreamRequest) returns (stream GetStoppedStreamResponse); } -message NodeInfo { +message NodeInformation { int32 process_id = 1; string app_protocol_version = 2; string swarm_end_point = 3; @@ -36,7 +36,7 @@ message StartRequest { } message StartResponse { - NodeInfo node_info = 1; + NodeInformation node_info = 1; } message StopRequest { @@ -49,14 +49,14 @@ message GetInfoRequest { } message GetInfoResponse { - NodeInfo node_info = 1; + NodeInformation node_info = 1; } message GetStartedStreamRequest { } message GetStartedStreamResponse { - NodeInfo node_info = 1; + NodeInformation node_info = 1; } message GetStoppedStreamRequest { From 5507d97520b026c1ae604d51d1acded57ef6bfc7 Mon Sep 17 00:00:00 2001 From: s2quake Date: Fri, 11 Oct 2024 13:45:49 +0900 Subject: [PATCH 10/18] refactor: Improve start up code --- .vscode/launch.json | 5 +- .../Application.cs | 59 +++++++++++++++++ .../EntryCommands/RunCommand.cs | 15 +---- .../EntryCommands/StartCommand.cs | 32 +-------- .../ServiceCollectionExtensions.cs | 29 --------- ...vice.cs => SystemTerminalHostedService.cs} | 39 ++++------- .../ClientHostedService.cs | 22 ++----- .../Application.cs | 61 +++++++++++++++++ .../EntryCommands/RunCommand.cs | 31 ++++----- .../EntryCommands/StartCommand.cs | 31 +-------- .../ServiceCollectionExtensions.cs | 32 --------- ...vice.cs => SystemTerminalHostedService.cs} | 22 ++----- .../Application.cs | 65 +++++++++++++++++++ .../EntryCommands/RunCommand.cs | 15 +---- .../EntryCommands/StartCommand.cs | 40 +----------- .../ServiceCollectionExtensions.cs | 34 ---------- ...vice.cs => SystemTerminalHostedService.cs} | 41 +++++------- .../Tracers/NodeEventTracer.cs | 4 +- src/node/LibplanetConsole.Node/Node.cs | 8 ++- .../NodeHostedService.cs | 54 ++------------- .../SeedHostedService.cs | 26 ++++++++ src/node/LibplanetConsole.Node/SeedService.cs | 12 ++-- .../ServiceCollectionExtensions.cs | 13 +++- 23 files changed, 309 insertions(+), 381 deletions(-) create mode 100644 src/client/LibplanetConsole.Client.Executable/Application.cs delete mode 100644 src/client/LibplanetConsole.Client.Executable/ServiceCollectionExtensions.cs rename src/client/LibplanetConsole.Client.Executable/{TerminalHostedService.cs => SystemTerminalHostedService.cs} (59%) create mode 100644 src/console/LibplanetConsole.Console.Executable/Application.cs delete mode 100644 src/console/LibplanetConsole.Console.Executable/ServiceCollectionExtensions.cs rename src/console/LibplanetConsole.Console.Executable/{ExecutableHostedService.cs => SystemTerminalHostedService.cs} (62%) create mode 100644 src/node/LibplanetConsole.Node.Executable/Application.cs delete mode 100644 src/node/LibplanetConsole.Node.Executable/ServiceCollectionExtensions.cs rename src/node/LibplanetConsole.Node.Executable/{TerminalHostedService.cs => SystemTerminalHostedService.cs} (59%) create mode 100644 src/node/LibplanetConsole.Node/SeedHostedService.cs diff --git a/.vscode/launch.json b/.vscode/launch.json index e4052eec..0de617bd 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -66,7 +66,8 @@ "program": "${workspaceFolder}/src/node/LibplanetConsole.Node.Executable/bin/Debug/net8.0/libplanet-node.dll", "console": "integratedTerminal", "args": [ - "run" + "run", + "--single-node" ] }, { @@ -141,4 +142,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/src/client/LibplanetConsole.Client.Executable/Application.cs b/src/client/LibplanetConsole.Client.Executable/Application.cs new file mode 100644 index 00000000..db6b4543 --- /dev/null +++ b/src/client/LibplanetConsole.Client.Executable/Application.cs @@ -0,0 +1,59 @@ +using JSSoft.Commands; +using LibplanetConsole.Client.Executable.Commands; +using LibplanetConsole.Client.Executable.Tracers; +using LibplanetConsole.Common; +using LibplanetConsole.Logging; +using Microsoft.AspNetCore.Server.Kestrel.Core; + +namespace LibplanetConsole.Client.Executable; + +internal sealed class Application +{ + private readonly WebApplicationBuilder _builder = WebApplication.CreateBuilder(); + + public Application(ApplicationOptions options, object[] instances) + { + var (_, port) = EndPointUtility.GetHostAndPort(options.EndPoint); + var services = _builder.Services; + foreach (var instance in instances) + { + services.AddSingleton(instance.GetType(), instance); + } + + _builder.WebHost.ConfigureKestrel(options => + { + options.ListenLocalhost(port, o => o.Protocols = HttpProtocols.Http2); + }); + + services.AddLogging(options.LogPath, options.LogPath); + + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton() + .AddSingleton(s => s.GetRequiredService()); + services.AddSingleton() + .AddSingleton(s => s.GetRequiredService()); + + services.AddClient(options); + + services.AddGrpc(); + services.AddGrpcReflection(); + + services.AddHostedService(); + services.AddHostedService(); + services.AddHostedService(); + } + + public async Task RunAsync(CancellationToken cancellationToken) + { + using var app = _builder.Build(); + + app.UseClient(); + app.MapGet("/", () => "Libplanet-Client"); + app.MapGrpcReflectionService().AllowAnonymous(); + + await Console.Out.WriteLineAsync(); + await app.RunAsync(cancellationToken); + } +} diff --git a/src/client/LibplanetConsole.Client.Executable/EntryCommands/RunCommand.cs b/src/client/LibplanetConsole.Client.Executable/EntryCommands/RunCommand.cs index 0e969502..d70a1537 100644 --- a/src/client/LibplanetConsole.Client.Executable/EntryCommands/RunCommand.cs +++ b/src/client/LibplanetConsole.Client.Executable/EntryCommands/RunCommand.cs @@ -27,18 +27,9 @@ protected override async Task OnExecuteAsync(CancellationToken cancellationToken { try { - // var serviceCollection = new ApplicationServiceCollection(_settingsCollection); - // var applicationOptions = _applicationSettings.ToOptions(); - - // serviceCollection.AddClient(applicationOptions); - // serviceCollection.AddApplication(applicationOptions); - - // await using var serviceProvider = serviceCollection.BuildServiceProvider(); - // var @out = Console.Out; - // var application = serviceProvider.GetRequiredService(); - // await @out.WriteLineAsync(); - // await application.RunAsync(); - // await @out.WriteLineAsync("\u001b0"); + var applicationOptions = _applicationSettings.ToOptions(); + var application = new Application(applicationOptions, [.. _settingsCollection]); + await application.RunAsync(cancellationToken); } catch (CommandParsingException e) { diff --git a/src/client/LibplanetConsole.Client.Executable/EntryCommands/StartCommand.cs b/src/client/LibplanetConsole.Client.Executable/EntryCommands/StartCommand.cs index 7c012b14..4671e488 100644 --- a/src/client/LibplanetConsole.Client.Executable/EntryCommands/StartCommand.cs +++ b/src/client/LibplanetConsole.Client.Executable/EntryCommands/StartCommand.cs @@ -31,8 +31,6 @@ protected override async Task OnExecuteAsync(CancellationToken cancellationToken { try { - var builder = WebApplication.CreateBuilder(); - var settingsPath = Path.Combine(RepositoryPath, Repository.SettingsFileName); var applicationSettings = Load(settingsPath) with { @@ -40,34 +38,8 @@ protected override async Task OnExecuteAsync(CancellationToken cancellationToken NoREPL = NoREPL, }; var applicationOptions = applicationSettings.ToOptions(); - - foreach (var settings in _settingsCollection) - { - builder.Services.AddSingleton(settings.GetType(), settings); - } - - var (_, port) = EndPointUtility.GetHostAndPort(applicationOptions.EndPoint); - builder.WebHost.ConfigureKestrel(options => - { - // Setup a HTTP/2 endpoint without TLS. - options.ListenLocalhost(port, o => o.Protocols = HttpProtocols.Http2); - // options.ListenLocalhost(port + 1, o => o.Protocols = HttpProtocols.Http1AndHttp2); - }); - - builder.Services.AddClient(applicationOptions); - builder.Services.AddExecutable(applicationOptions); - - builder.Services.AddGrpc(); - builder.Services.AddGrpcReflection(); - - using var app = builder.Build(); - - app.UseClient(); - app.MapGet("/", () => "123"); - - var @out = Console.Out; - await @out.WriteLineAsync(); - await app.RunAsync(cancellationToken); + var application = new Application(applicationOptions, [.. _settingsCollection]); + await application.RunAsync(cancellationToken); } catch (CommandParsingException e) { diff --git a/src/client/LibplanetConsole.Client.Executable/ServiceCollectionExtensions.cs b/src/client/LibplanetConsole.Client.Executable/ServiceCollectionExtensions.cs deleted file mode 100644 index 6e7621a4..00000000 --- a/src/client/LibplanetConsole.Client.Executable/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,29 +0,0 @@ -using JSSoft.Commands; -using LibplanetConsole.Client.Executable.Commands; -using LibplanetConsole.Client.Executable.Tracers; -using LibplanetConsole.Logging; - -namespace LibplanetConsole.Client.Executable; - -internal static class ServiceCollectionExtensions -{ - public static IServiceCollection AddExecutable( - this IServiceCollection @this, ApplicationOptions options) - { - @this.AddLogging(options.LogPath, options.LogPath); - - @this.AddSingleton(); - @this.AddSingleton(); - @this.AddHostedService(); - - @this.AddSingleton() - .AddSingleton(s => s.GetRequiredService()); - @this.AddSingleton() - .AddSingleton(s => s.GetRequiredService()); - - @this.AddHostedService(); - @this.AddHostedService(); - - return @this; - } -} diff --git a/src/client/LibplanetConsole.Client.Executable/TerminalHostedService.cs b/src/client/LibplanetConsole.Client.Executable/SystemTerminalHostedService.cs similarity index 59% rename from src/client/LibplanetConsole.Client.Executable/TerminalHostedService.cs rename to src/client/LibplanetConsole.Client.Executable/SystemTerminalHostedService.cs index 904b7069..5cd3ed1a 100644 --- a/src/client/LibplanetConsole.Client.Executable/TerminalHostedService.cs +++ b/src/client/LibplanetConsole.Client.Executable/SystemTerminalHostedService.cs @@ -5,15 +5,13 @@ namespace LibplanetConsole.Client.Executable; -internal sealed class TerminalHostedService( +internal sealed class SystemTerminalHostedService( IHostApplicationLifetime applicationLifetime, CommandContext commandContext, SystemTerminal terminal, ApplicationOptions options, - ILogger logger) : IHostedService, IDisposable + ILogger logger) : IHostedService { - private readonly CancellationTokenSource _cancellationTokenSource = new(); - private Task _runningTask = Task.CompletedTask; private Task _waitInputTask = Task.CompletedTask; private Task _waitForExitTask = Task.CompletedTask; private int _parentProcessId; @@ -22,25 +20,18 @@ public async Task StartAsync(CancellationToken cancellationToken) { if (options.NoREPL != true) { - applicationLifetime.ApplicationStarted.Register(async () => - { - var sw = new StringWriter(); - commandContext.Out = sw; - await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); - await commandContext.ExecuteAsync(["--help"], cancellationToken: default); - await sw.WriteLineAsync(); - await commandContext.ExecuteAsync(args: [], cancellationToken: default); - await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); - commandContext.Out = Console.Out; - await sw.WriteLineIfAsync(GetStartupCondition(options), GetStartupMessage()); - await Console.Out.WriteAsync(sw.ToString()); + var sw = new StringWriter(); + commandContext.Out = sw; + await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); + await commandContext.ExecuteAsync(["--help"], cancellationToken); + await sw.WriteLineAsync(); + await commandContext.ExecuteAsync(args: [], cancellationToken); + await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); + commandContext.Out = Console.Out; + await sw.WriteLineIfAsync(GetStartupCondition(options), GetStartupMessage()); + await Console.Out.WriteAsync(sw.ToString()); - _runningTask = terminal.StartAsync(_cancellationTokenSource.Token); - }); - applicationLifetime.ApplicationStopping.Register(() => - { - _cancellationTokenSource.Cancel(); - }); + await terminal.StartAsync(cancellationToken); } else if (options.ParentProcessId != 0 && Process.GetProcessById(options.ParentProcessId) is { } parentProcess) @@ -58,12 +49,10 @@ public async Task StartAsync(CancellationToken cancellationToken) public async Task StopAsync(CancellationToken cancellationToken) { - await Task.WhenAll(_runningTask, _waitInputTask, _waitForExitTask); + await Task.WhenAll(_waitInputTask, _waitForExitTask); await terminal.StopAsync(cancellationToken); } - void IDisposable.Dispose() => _cancellationTokenSource.Dispose(); - private static bool GetStartupCondition(ApplicationOptions options) { if (options.NodeEndPoint is not null) diff --git a/src/client/LibplanetConsole.Client/ClientHostedService.cs b/src/client/LibplanetConsole.Client/ClientHostedService.cs index 3f968dad..9163a5b0 100644 --- a/src/client/LibplanetConsole.Client/ClientHostedService.cs +++ b/src/client/LibplanetConsole.Client/ClientHostedService.cs @@ -1,27 +1,17 @@ using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; namespace LibplanetConsole.Client; -internal sealed class ClientHostedService( - IHostApplicationLifetime applicationLifetime, - ApplicationOptions options, - Client client, - ILogger logger) +internal sealed class ClientHostedService(Client client, ApplicationOptions options) : IHostedService { - public Task StartAsync(CancellationToken cancellationToken) + public async Task StartAsync(CancellationToken cancellationToken) { - applicationLifetime.ApplicationStarted.Register(async () => + if (options.NodeEndPoint is not null) { - if (options.NodeEndPoint is not null) - { - logger.LogDebug("Client auto-starting"); - await client.StartAsync(cancellationToken); - logger.LogDebug("Client auto-started"); - } - }); - return Task.CompletedTask; + client.NodeEndPoint = options.NodeEndPoint; + await client.StartAsync(cancellationToken); + } } public async Task StopAsync(CancellationToken cancellationToken) diff --git a/src/console/LibplanetConsole.Console.Executable/Application.cs b/src/console/LibplanetConsole.Console.Executable/Application.cs new file mode 100644 index 00000000..81b01861 --- /dev/null +++ b/src/console/LibplanetConsole.Console.Executable/Application.cs @@ -0,0 +1,61 @@ +using JSSoft.Commands; +using LibplanetConsole.Common; +using LibplanetConsole.Console.Evidence; +using LibplanetConsole.Console.Executable.Commands; +using LibplanetConsole.Console.Executable.Tracers; +using LibplanetConsole.Logging; +using Microsoft.AspNetCore.Server.Kestrel.Core; + +namespace LibplanetConsole.Console.Executable; + +internal sealed class Application +{ + private readonly WebApplicationBuilder _builder = WebApplication.CreateBuilder(); + + public Application(ApplicationOptions options, object[] instances) + { + var (_, port) = EndPointUtility.GetHostAndPort(options.EndPoint); + var services = _builder.Services; + + foreach (var instance in instances) + { + services.AddSingleton(instance.GetType(), instance); + } + + _builder.WebHost.ConfigureKestrel(options => + { + options.ListenLocalhost(port, o => o.Protocols = HttpProtocols.Http2); + }); + + services.AddLogging(options.LogPath, options.LibraryLogPath); + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton() + .AddSingleton(s => s.GetRequiredService()); + services.AddSingleton() + .AddSingleton(s => s.GetRequiredService()); + + services.AddEvidence(); + services.AddConsole(options); + + services.AddGrpc(); + services.AddGrpcReflection(); + + services.AddHostedService(); + services.AddHostedService(); + services.AddHostedService(); + } + + public async Task RunAsync(CancellationToken cancellationToken) + { + using var app = _builder.Build(); + + app.UseConsole(); + app.MapGet("/", () => "Libplanet-Console"); + app.MapGrpcReflectionService().AllowAnonymous(); + + await System.Console.Out.WriteLineAsync(); + await app.RunAsync(cancellationToken); + } +} diff --git a/src/console/LibplanetConsole.Console.Executable/EntryCommands/RunCommand.cs b/src/console/LibplanetConsole.Console.Executable/EntryCommands/RunCommand.cs index 1808d77a..bd98f770 100644 --- a/src/console/LibplanetConsole.Console.Executable/EntryCommands/RunCommand.cs +++ b/src/console/LibplanetConsole.Console.Executable/EntryCommands/RunCommand.cs @@ -26,26 +26,17 @@ object ICustomCommandDescriptor.GetMemberOwner(CommandMemberDescriptor memberDes protected override async Task OnExecuteAsync(CancellationToken cancellationToken) { - // try - // { - // var serviceCollection = new ApplicationServiceCollection(_settingsCollection); - // var applicationOptions = _applicationSettings.ToOptions(); - - // serviceCollection.AddConsole(applicationOptions); - // serviceCollection.AddApplication(applicationOptions); - - // await using var serviceProvider = serviceCollection.BuildServiceProvider(); - // var @out = System.Console.Out; - // var application = serviceProvider.GetRequiredService(); - // await @out.WriteLineAsync(); - // await application.RunAsync(); - // await @out.WriteLineAsync("\u001b0"); - // } - // catch (CommandParsingException e) - // { - // e.Print(System.Console.Out); - // Environment.Exit(1); - // } + try + { + var applicationOptions = _applicationSettings.ToOptions(); + var application = new Application(applicationOptions, [.. _settingsCollection]); + await application.RunAsync(cancellationToken); + } + catch (CommandParsingException e) + { + e.Print(System.Console.Out); + Environment.Exit(1); + } } private static Dictionary GetDescriptors(object[] options) diff --git a/src/console/LibplanetConsole.Console.Executable/EntryCommands/StartCommand.cs b/src/console/LibplanetConsole.Console.Executable/EntryCommands/StartCommand.cs index 25f07f1a..3b2f73f1 100644 --- a/src/console/LibplanetConsole.Console.Executable/EntryCommands/StartCommand.cs +++ b/src/console/LibplanetConsole.Console.Executable/EntryCommands/StartCommand.cs @@ -21,8 +21,6 @@ protected override async Task OnExecuteAsync(CancellationToken cancellationToken { try { - var builder = WebApplication.CreateBuilder(); - var resolver = new RepositoryPathResolver(); var repositoryPath = Path.GetFullPath(RepositoryPath); var settingsPath = resolver.GetSettingsPath(repositoryPath); @@ -35,33 +33,8 @@ protected override async Task OnExecuteAsync(CancellationToken cancellationToken LibraryLogPath = applicationSettings.LibraryLogPath, }; - foreach (var settings in _settingsCollection) - { - builder.Services.AddSingleton(settings.GetType(), settings); - } - - var (_, port) = EndPointUtility.GetHostAndPort(applicationOptions.EndPoint); - builder.WebHost.ConfigureKestrel(options => - { - // Setup a HTTP/2 endpoint without TLS. - options.ListenLocalhost(port, o => o.Protocols = HttpProtocols.Http2); - // options.ListenLocalhost(port + 1, o => o.Protocols = HttpProtocols.Http1AndHttp2); - }); - - builder.Services.AddConsole(applicationOptions); - builder.Services.AddExecutable(applicationOptions); - - builder.Services.AddGrpc(); - builder.Services.AddGrpcReflection(); - - using var app = builder.Build(); - - app.UseConsole(); - app.MapGet("/", () => "Libplanet Console"); - - var @out = System.Console.Out; - await @out.WriteLineAsync(); - await app.RunAsync(cancellationToken); + var application = new Application(applicationOptions, [.. _settingsCollection]); + await application.RunAsync(cancellationToken); } catch (CommandParsingException e) { diff --git a/src/console/LibplanetConsole.Console.Executable/ServiceCollectionExtensions.cs b/src/console/LibplanetConsole.Console.Executable/ServiceCollectionExtensions.cs deleted file mode 100644 index 401a11af..00000000 --- a/src/console/LibplanetConsole.Console.Executable/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using JSSoft.Commands; -using LibplanetConsole.Console.Evidence; -using LibplanetConsole.Console.Executable.Commands; -using LibplanetConsole.Console.Executable.Tracers; -using LibplanetConsole.Logging; - -namespace LibplanetConsole.Console.Executable; - -internal static class ServiceCollectionExtensions -{ - public static IServiceCollection AddExecutable( - this IServiceCollection @this, ApplicationOptions options) - { - @this.AddLogging(options.LogPath, options.LibraryLogPath); - - @this.AddSingleton(); - @this.AddSingleton(); - @this.AddHostedService(); - - @this.AddSingleton() - .AddSingleton(s => s.GetRequiredService()); - @this.AddSingleton() - .AddSingleton(s => s.GetRequiredService()); - - @this.AddHostedService(); - @this.AddHostedService(); - - @this.AddEvidence(); - - return @this; - } -} diff --git a/src/console/LibplanetConsole.Console.Executable/ExecutableHostedService.cs b/src/console/LibplanetConsole.Console.Executable/SystemTerminalHostedService.cs similarity index 62% rename from src/console/LibplanetConsole.Console.Executable/ExecutableHostedService.cs rename to src/console/LibplanetConsole.Console.Executable/SystemTerminalHostedService.cs index 658c72da..ddb7c8fd 100644 --- a/src/console/LibplanetConsole.Console.Executable/ExecutableHostedService.cs +++ b/src/console/LibplanetConsole.Console.Executable/SystemTerminalHostedService.cs @@ -4,14 +4,9 @@ namespace LibplanetConsole.Console.Executable; -internal sealed class ExecutableHostedService( - IHostApplicationLifetime applicationLifetime, - CommandContext commandContext, - SystemTerminal terminal) : IHostedService, IDisposable +internal sealed class SystemTerminalHostedService( + CommandContext commandContext, SystemTerminal terminal) : IHostedService { - private readonly CancellationTokenSource _cancellationTokenSource = new(); - private Task _runningTask = Task.CompletedTask; - public async Task StartAsync(CancellationToken cancellationToken) { var message = "Welcome to console for Libplanet."; @@ -19,27 +14,18 @@ public async Task StartAsync(CancellationToken cancellationToken) commandContext.Out = sw; await System.Console.Out.WriteColoredLineAsync(message, TerminalColorType.BrightGreen); await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); - await commandContext.ExecuteAsync(["--help"], cancellationToken: default); + await commandContext.ExecuteAsync(["--help"], cancellationToken); await sw.WriteLineAsync(); - await commandContext.ExecuteAsync(args: [], cancellationToken: default); + await commandContext.ExecuteAsync(args: [], cancellationToken); await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); commandContext.Out = System.Console.Out; await System.Console.Out.WriteAsync(sw.ToString()); await terminal.StartAsync(cancellationToken); - applicationLifetime.ApplicationStopping.Register(() => - { - _cancellationTokenSource.Cancel(); - }); - - await Task.CompletedTask; } public async Task StopAsync(CancellationToken cancellationToken) { - await _runningTask; await terminal.StopAsync(cancellationToken); } - - void IDisposable.Dispose() => _cancellationTokenSource.Dispose(); } diff --git a/src/node/LibplanetConsole.Node.Executable/Application.cs b/src/node/LibplanetConsole.Node.Executable/Application.cs new file mode 100644 index 00000000..549b3a39 --- /dev/null +++ b/src/node/LibplanetConsole.Node.Executable/Application.cs @@ -0,0 +1,65 @@ +using JSSoft.Commands; +using LibplanetConsole.Common; +using LibplanetConsole.Logging; +using LibplanetConsole.Node.Evidence; +using LibplanetConsole.Node.Executable.Commands; +using LibplanetConsole.Node.Executable.Tracers; +using LibplanetConsole.Node.Explorer; +using Microsoft.AspNetCore.Server.Kestrel.Core; + +namespace LibplanetConsole.Node.Executable; + +internal sealed class Application +{ + private readonly WebApplicationBuilder _builder = WebApplication.CreateBuilder(); + + public Application(ApplicationOptions options, object[] instances) + { + var (_, port) = EndPointUtility.GetHostAndPort(options.EndPoint); + var services = _builder.Services; + foreach (var instance in instances) + { + services.AddSingleton(instance.GetType(), instance); + } + + _builder.WebHost.ConfigureKestrel(options => + { + options.ListenLocalhost(port, o => o.Protocols = HttpProtocols.Http2); + }); + + services.AddLogging(options.LogPath, options.LibraryLogPath); + + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton() + .AddSingleton(s => s.GetRequiredService()); + services.AddSingleton() + .AddSingleton(s => s.GetRequiredService()); + + services.AddNode(options); + services.AddExplorer(_builder.Configuration); + + services.AddGrpc(); + services.AddGrpcReflection(); + + services.AddHostedService(); + services.AddHostedService(); + services.AddHostedService(); + } + + public async Task RunAsync(CancellationToken cancellationToken) + { + using var app = _builder.Build(); + + app.UseNode(); + app.UseExplorer(); + app.MapGet("/", () => "Libplanet-Node"); + app.UseAuthentication(); + app.UseAuthorization(); + app.MapGrpcReflectionService().AllowAnonymous(); + + await Console.Out.WriteLineAsync(); + await app.RunAsync(cancellationToken); + } +} diff --git a/src/node/LibplanetConsole.Node.Executable/EntryCommands/RunCommand.cs b/src/node/LibplanetConsole.Node.Executable/EntryCommands/RunCommand.cs index 607f4f04..e6793f0d 100644 --- a/src/node/LibplanetConsole.Node.Executable/EntryCommands/RunCommand.cs +++ b/src/node/LibplanetConsole.Node.Executable/EntryCommands/RunCommand.cs @@ -27,18 +27,9 @@ protected override async Task OnExecuteAsync(CancellationToken cancellationToken { try { - // var serviceCollection = new ApplicationServiceCollection(_settingsCollection); - // var applicationOptions = _applicationSettings.ToOptions(); - - // serviceCollection.AddNode(applicationOptions); - // serviceCollection.AddApplication(applicationOptions); - - // await using var serviceProvider = serviceCollection.BuildServiceProvider(); - // var @out = Console.Out; - // var application = serviceProvider.GetRequiredService(); - // await @out.WriteLineAsync(); - // await application.RunAsync(); - // await @out.WriteLineAsync("\u001b0"); + var applicationOptions = _applicationSettings.ToOptions(); + var application = new Application(applicationOptions, [.. _settingsCollection]); + await application.RunAsync(cancellationToken); } catch (CommandParsingException e) { diff --git a/src/node/LibplanetConsole.Node.Executable/EntryCommands/StartCommand.cs b/src/node/LibplanetConsole.Node.Executable/EntryCommands/StartCommand.cs index 08d8fc4e..20855e76 100644 --- a/src/node/LibplanetConsole.Node.Executable/EntryCommands/StartCommand.cs +++ b/src/node/LibplanetConsole.Node.Executable/EntryCommands/StartCommand.cs @@ -1,11 +1,8 @@ using System.ComponentModel; using JSSoft.Commands; -using LibplanetConsole.Common; using LibplanetConsole.DataAnnotations; using LibplanetConsole.Framework; -using LibplanetConsole.Node.Explorer; using LibplanetConsole.Settings; -using Microsoft.AspNetCore.Server.Kestrel.Core; namespace LibplanetConsole.Node.Executable.EntryCommands; @@ -37,8 +34,6 @@ protected override async Task OnExecuteAsync(CancellationToken cancellationToken { try { - var builder = WebApplication.CreateBuilder(); - var settingsPath = Path.Combine(RepositoryPath, Repository.SettingsFileName); var applicationSettings = Load(settingsPath) with { @@ -46,39 +41,8 @@ protected override async Task OnExecuteAsync(CancellationToken cancellationToken NoREPL = NoREPL, }; var applicationOptions = applicationSettings.ToOptions(); - - foreach (var settings in _settingsCollection) - { - builder.Services.AddSingleton(settings.GetType(), settings); - } - - var (_, port) = EndPointUtility.GetHostAndPort(applicationOptions.EndPoint); - builder.WebHost.ConfigureKestrel(options => - { - // Setup a HTTP/2 endpoint without TLS. - options.ListenLocalhost(port, o => o.Protocols = HttpProtocols.Http2); - // options.ListenLocalhost(port + 1, o => o.Protocols = HttpProtocols.Http1AndHttp2); - }); - - builder.Services.AddNode(applicationOptions); - builder.Services.AddApplication(applicationOptions); - builder.Services.AddExplorer(builder.Configuration); - - builder.Services.AddGrpc(); - builder.Services.AddGrpcReflection(); - - using var app = builder.Build(); - - app.UseNode(); - app.UseExplorer(); - app.MapGet("/", () => "123"); - app.UseAuthentication(); - app.UseAuthorization(); - app.MapGrpcReflectionService().AllowAnonymous(); - - var @out = Console.Out; - await @out.WriteLineAsync(); - await app.RunAsync(cancellationToken); + var application = new Application(applicationOptions, [.. _settingsCollection]); + await application.RunAsync(cancellationToken); } catch (CommandParsingException e) { diff --git a/src/node/LibplanetConsole.Node.Executable/ServiceCollectionExtensions.cs b/src/node/LibplanetConsole.Node.Executable/ServiceCollectionExtensions.cs deleted file mode 100644 index 8e5171cd..00000000 --- a/src/node/LibplanetConsole.Node.Executable/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,34 +0,0 @@ -using JSSoft.Commands; -using LibplanetConsole.Logging; -using LibplanetConsole.Node.Evidence; -using LibplanetConsole.Node.Executable.Commands; -using LibplanetConsole.Node.Executable.Tracers; - -namespace LibplanetConsole.Node.Executable; - -internal static class ServiceCollectionExtensions -{ - public static IServiceCollection AddApplication( - this IServiceCollection @this, ApplicationOptions options) - { - @this.AddLogging(options.LogPath, options.LibraryLogPath); - - @this.AddSingleton(); - @this.AddSingleton(); - @this.AddHostedService(); - - @this.AddSingleton() - .AddSingleton(s => s.GetRequiredService()); - @this.AddSingleton() - .AddSingleton(s => s.GetRequiredService()); - - @this.AddHostedService(); - @this.AddHostedService(); - - @this.AddEvidence(); - - @this.AddGrpc(); - - return @this; - } -} diff --git a/src/node/LibplanetConsole.Node.Executable/TerminalHostedService.cs b/src/node/LibplanetConsole.Node.Executable/SystemTerminalHostedService.cs similarity index 59% rename from src/node/LibplanetConsole.Node.Executable/TerminalHostedService.cs rename to src/node/LibplanetConsole.Node.Executable/SystemTerminalHostedService.cs index e70577dc..51c2c358 100644 --- a/src/node/LibplanetConsole.Node.Executable/TerminalHostedService.cs +++ b/src/node/LibplanetConsole.Node.Executable/SystemTerminalHostedService.cs @@ -5,16 +5,14 @@ namespace LibplanetConsole.Node.Executable; -internal sealed class TerminalHostedService( +internal sealed class SystemTerminalHostedService( IHostApplicationLifetime applicationLifetime, CommandContext commandContext, SystemTerminal terminal, ApplicationOptions options, - ILogger logger) - : IHostedService, IDisposable + ILogger logger) + : IHostedService { - private readonly CancellationTokenSource _cancellationTokenSource = new(); - private Task _runningTask = Task.CompletedTask; private Task _waitInputTask = Task.CompletedTask; private Task _waitForExitTask = Task.CompletedTask; private int _parentProcessId; @@ -23,25 +21,18 @@ public async Task StartAsync(CancellationToken cancellationToken) { if (options.NoREPL is false) { - applicationLifetime.ApplicationStarted.Register(async () => - { - var sw = new StringWriter(); - commandContext.Out = sw; - await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); - await commandContext.ExecuteAsync(["--help"], cancellationToken: default); - await sw.WriteLineAsync(); - await commandContext.ExecuteAsync(args: [], cancellationToken: default); - await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); - commandContext.Out = Console.Out; - await sw.WriteLineIfAsync(GetStartupCondition(options), GetStartupMessage()); - await Console.Out.WriteAsync(sw.ToString()); + var sw = new StringWriter(); + commandContext.Out = sw; + await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); + await commandContext.ExecuteAsync(["--help"], cancellationToken); + await sw.WriteLineAsync(); + await commandContext.ExecuteAsync(args: [], cancellationToken); + await sw.WriteSeparatorAsync(TerminalColorType.BrightGreen); + commandContext.Out = Console.Out; + await sw.WriteLineIfAsync(GetStartupCondition(options), GetStartupMessage()); + await Console.Out.WriteAsync(sw.ToString()); - _runningTask = terminal.StartAsync(_cancellationTokenSource.Token); - }); - applicationLifetime.ApplicationStopping.Register(() => - { - _cancellationTokenSource.Cancel(); - }); + await terminal.StartAsync(cancellationToken); } else if (options.ParentProcessId != 0 && Process.GetProcessById(options.ParentProcessId) is { } parentProcess) @@ -59,12 +50,10 @@ public async Task StartAsync(CancellationToken cancellationToken) public async Task StopAsync(CancellationToken cancellationToken) { - await Task.WhenAll(_runningTask, _waitInputTask, _waitForExitTask); + await Task.WhenAll(_waitInputTask, _waitForExitTask); await terminal.StopAsync(cancellationToken); } - void IDisposable.Dispose() => _cancellationTokenSource.Dispose(); - private static bool GetStartupCondition(ApplicationOptions options) { if (options.SeedEndPoint is not null) diff --git a/src/node/LibplanetConsole.Node.Executable/Tracers/NodeEventTracer.cs b/src/node/LibplanetConsole.Node.Executable/Tracers/NodeEventTracer.cs index a5c2f73f..b76f6eae 100644 --- a/src/node/LibplanetConsole.Node.Executable/Tracers/NodeEventTracer.cs +++ b/src/node/LibplanetConsole.Node.Executable/Tracers/NodeEventTracer.cs @@ -31,14 +31,14 @@ void IDisposable.Dispose() private void Node_Started(object? sender, EventArgs e) { var endPoint = _options.EndPoint; - var message = $"BlockChain has been started.: {endPoint}"; + 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 message = $"BlockChain has been stopped.: {endPoint}"; + var message = $"Node has been stopped.: {endPoint}"; Console.Out.WriteColoredLine(message, TerminalColorType.BrightGreen); } } diff --git a/src/node/LibplanetConsole.Node/Node.cs b/src/node/LibplanetConsole.Node/Node.cs index 27baa671..8b954d7a 100644 --- a/src/node/LibplanetConsole.Node/Node.cs +++ b/src/node/LibplanetConsole.Node/Node.cs @@ -13,6 +13,7 @@ using LibplanetConsole.Common.Exceptions; using LibplanetConsole.Common.Extensions; using LibplanetConsole.Seed; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace LibplanetConsole.Node; @@ -298,9 +299,14 @@ private static async Task CreateTransport( return await NetMQTransport.Create(privateKey, appProtocolVersionOptions, hostOptions); } - private static async Task GetSeedInfoAsync( + private async Task GetSeedInfoAsync( EndPoint seedEndPoint, ILogger logger, CancellationToken cancellationToken) { + if (_serviceProvider.GetService() is { } seedService) + { + return await seedService.GetSeedAsync(PublicKey, cancellationToken); + } + logger.LogDebug("Getting seed info from {SeedEndPoint}", seedEndPoint); var address = $"http://{EndPointUtility.ToString(seedEndPoint)}"; var channelOptions = new GrpcChannelOptions diff --git a/src/node/LibplanetConsole.Node/NodeHostedService.cs b/src/node/LibplanetConsole.Node/NodeHostedService.cs index 00da0f80..d58bd803 100644 --- a/src/node/LibplanetConsole.Node/NodeHostedService.cs +++ b/src/node/LibplanetConsole.Node/NodeHostedService.cs @@ -1,36 +1,17 @@ using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using static LibplanetConsole.Common.EndPointUtility; namespace LibplanetConsole.Node; -internal sealed class NodeHostedService( - IHostApplicationLifetime applicationLifetime, - SeedService seedService, - Node node, - ApplicationOptions options, - ILogger logger) +internal sealed class NodeHostedService(Node node, ApplicationOptions options) : IHostedService { - private CancellationTokenSource? _cancellationTokenSource; - - public Task StartAsync(CancellationToken cancellationToken) + public async Task StartAsync(CancellationToken cancellationToken) { - applicationLifetime.ApplicationStarted.Register(async () => + if (options.SeedEndPoint is { } seedEndPoint) { - _cancellationTokenSource = new(); - try - { - await ExecuteAsync(_cancellationTokenSource.Token); - } - catch (Exception e) - { - logger.LogError(e, "An error occurred while starting the node."); - applicationLifetime.StopApplication(); - } - }); - - return Task.CompletedTask; + node.SeedEndPoint = seedEndPoint; + await node.StartAsync(cancellationToken); + } } public async Task StopAsync(CancellationToken cancellationToken) @@ -39,28 +20,5 @@ public async Task StopAsync(CancellationToken cancellationToken) { await node.StopAsync(cancellationToken); } - - await seedService.StopAsync(cancellationToken); - } - - private async Task ExecuteAsync(CancellationToken cancellationToken) - { - if (_cancellationTokenSource is not null) - { - await _cancellationTokenSource.CancelAsync(); - _cancellationTokenSource.Dispose(); - _cancellationTokenSource = null; - } - - if (CompareEndPoint(options.SeedEndPoint, options.EndPoint) is true) - { - await seedService.StartAsync(cancellationToken); - } - - if (options.SeedEndPoint is { } seedEndPoint) - { - node.SeedEndPoint = seedEndPoint; - await node.StartAsync(cancellationToken); - } } } diff --git a/src/node/LibplanetConsole.Node/SeedHostedService.cs b/src/node/LibplanetConsole.Node/SeedHostedService.cs new file mode 100644 index 00000000..22209581 --- /dev/null +++ b/src/node/LibplanetConsole.Node/SeedHostedService.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace LibplanetConsole.Node; + +internal sealed class SeedHostedService(IServiceProvider serviceProvider) + : IHostedService +{ + private readonly SeedService? _seedService = serviceProvider.GetService(); + + public async Task StartAsync(CancellationToken cancellationToken) + { + if (_seedService is not null) + { + await _seedService.StartAsync(cancellationToken); + } + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + if (_seedService is { IsRunning: true }) + { + await _seedService.StopAsync(cancellationToken); + } + } +} diff --git a/src/node/LibplanetConsole.Node/SeedService.cs b/src/node/LibplanetConsole.Node/SeedService.cs index 54a42f13..ea8f1600 100644 --- a/src/node/LibplanetConsole.Node/SeedService.cs +++ b/src/node/LibplanetConsole.Node/SeedService.cs @@ -9,6 +9,8 @@ internal sealed class SeedService : ISeedService private SeedNode? _blocksyncSeedNode; private SeedNode? _consensusSeedNode; + public bool IsRunning => _blocksyncSeedNode is not null && _consensusSeedNode is not null; + public Task GetSeedAsync( PublicKey publicKey, CancellationToken cancellationToken) { @@ -30,18 +32,20 @@ public Task GetSeedAsync( public async Task StartAsync(CancellationToken cancellationToken) { - _blocksyncSeedNode = new SeedNode(new() + var blocksyncSeedNode = new SeedNode(new() { PrivateKey = _seedNodePrivateKey, EndPoint = NextEndPoint(), }); - _consensusSeedNode = new SeedNode(new() + var consensusSeedNode = new SeedNode(new() { PrivateKey = _seedNodePrivateKey, EndPoint = NextEndPoint(), }); - await _blocksyncSeedNode.StartAsync(cancellationToken); - await _consensusSeedNode.StartAsync(cancellationToken); + await blocksyncSeedNode.StartAsync(cancellationToken); + await consensusSeedNode.StartAsync(cancellationToken); + _blocksyncSeedNode = blocksyncSeedNode; + _consensusSeedNode = consensusSeedNode; } public async Task StopAsync(CancellationToken cancellationToken) diff --git a/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs b/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs index 9583e68d..bfc65575 100644 --- a/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs +++ b/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs @@ -3,6 +3,7 @@ using LibplanetConsole.Node.Commands; using LibplanetConsole.Seed; using Microsoft.Extensions.DependencyInjection; +using static LibplanetConsole.Common.EndPointUtility; namespace LibplanetConsole.Node; @@ -15,15 +16,21 @@ public static IServiceCollection AddNode( SynchronizationContext.SetSynchronizationContext(synchronizationContext); @this.AddSingleton(synchronizationContext); @this.AddSingleton(options); - @this.AddSingleton() - .AddSingleton(s => s.GetRequiredService()); + if (CompareEndPoint(options.SeedEndPoint, options.EndPoint) is true) + { + @this.AddSingleton() + .AddSingleton(s => s.GetRequiredService()); + } + @this.AddSingleton() .AddSingleton(s => s.GetRequiredService()) .AddSingleton(s => s.GetRequiredService()); - @this.AddHostedService(); @this.AddSingleton(); @this.AddSingleton(); + @this.AddHostedService(); + @this.AddHostedService(); + @this.AddSingleton(); @this.AddSingleton(); @this.AddSingleton(); From f3073087744b9c1d8e6ab236f707bea38110905b Mon Sep 17 00:00:00 2001 From: s2quake Date: Fri, 11 Oct 2024 15:15:34 +0900 Subject: [PATCH 11/18] refactor: Improve log system --- README.md | 13 +++--- .../Application.cs | 6 ++- .../EntryCommands/InitializeCommand.cs | 2 +- .../Application.cs | 9 +++- .../ApplicationSettings.cs | 8 +--- .../EntryCommands/InitializeCommand.cs | 8 ++-- .../EntryCommands/StartCommand.cs | 1 - .../Repository.cs | 3 -- .../ApplicationOptions.cs | 2 - .../LibplanetConsole.Console/NodeOptions.cs | 6 --- .../Application.cs | 11 ++++- .../ApplicationSettings.cs | 12 +---- .../EntryCommands/InitializeCommand.cs | 12 +---- .../Repository.cs | 3 -- .../ApplicationOptions.cs | 2 - .../LoggingExtensions.cs | 44 ++++--------------- .../LibplanetConsole.Logging/LoggingFilter.cs | 7 +++ .../LibplanetConsole.Logging/PrefixFilter.cs | 6 +++ .../SourceContextFilter.cs | 27 ++++++++++++ 19 files changed, 86 insertions(+), 96 deletions(-) create mode 100644 src/shared/LibplanetConsole.Logging/LoggingFilter.cs create mode 100644 src/shared/LibplanetConsole.Logging/PrefixFilter.cs create mode 100644 src/shared/LibplanetConsole.Logging/SourceContextFilter.cs diff --git a/README.md b/README.md index aeba0ac4..465956a4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Libplanet Console -This repository provides a `REPL` environment that enables easy testing of +This repository provides a `REPL` environment that enables easy testing of libplanet features. ## Requirements @@ -27,7 +27,7 @@ dotnet publish # Creating Repository -Run the following command to create a repository for starting 4 nodes +Run the following command to create a repository for starting 4 nodes and 2 clients at the specified path. ```sh @@ -55,9 +55,9 @@ Create and run a repository for a single node. ## Settings data -Once the repository is created, the specified path will contain -`node-settings.json` and `client-settings.json` files as shown below. -Users can configure various values to run nodes and clients +Once the repository is created, the specified path will contain +`node-settings.json` and `client-settings.json` files as shown below. +Users can configure various values to run nodes and clients in different environments. ```json @@ -67,9 +67,8 @@ in different environments. "endPoint": "localhost:55314", "privateKey": "5a3df2ce7fc8b8f7c984f867a34e7d343e974f7b661c83536c0a66685bdbf04a", "storePath": "store", + "logPath": "log", "genesisPath": "genesis", - "logPath": "app.log", - "libraryLogPath": "library.log" } } ``` diff --git a/src/client/LibplanetConsole.Client.Executable/Application.cs b/src/client/LibplanetConsole.Client.Executable/Application.cs index db6b4543..22a34c3b 100644 --- a/src/client/LibplanetConsole.Client.Executable/Application.cs +++ b/src/client/LibplanetConsole.Client.Executable/Application.cs @@ -10,6 +10,10 @@ namespace LibplanetConsole.Client.Executable; internal sealed class Application { private readonly WebApplicationBuilder _builder = WebApplication.CreateBuilder(); + private readonly LoggingFilter[] _filters = + [ + new PrefixFilter("app", "LibplanetConsole."), + ]; public Application(ApplicationOptions options, object[] instances) { @@ -25,7 +29,7 @@ public Application(ApplicationOptions options, object[] instances) options.ListenLocalhost(port, o => o.Protocols = HttpProtocols.Http2); }); - services.AddLogging(options.LogPath, options.LogPath); + services.AddLogging(options.LogPath, "client.log", _filters); services.AddSingleton(); services.AddSingleton(); diff --git a/src/client/LibplanetConsole.Client.Executable/EntryCommands/InitializeCommand.cs b/src/client/LibplanetConsole.Client.Executable/EntryCommands/InitializeCommand.cs index 469a0f5c..541ae4d8 100644 --- a/src/client/LibplanetConsole.Client.Executable/EntryCommands/InitializeCommand.cs +++ b/src/client/LibplanetConsole.Client.Executable/EntryCommands/InitializeCommand.cs @@ -47,7 +47,7 @@ protected override void OnExecute() var outputPath = Path.GetFullPath(OutputPath); var endPoint = EndPointUtility.ParseOrNext(EndPoint); var privateKey = PrivateKeyUtility.ParseOrRandom(PrivateKey); - var logPath = Path.Combine(outputPath, LogPath.Fallback("app.log")); + var logPath = Path.Combine(outputPath, LogPath.Fallback("log")); var repository = new Repository { EndPoint = endPoint, diff --git a/src/console/LibplanetConsole.Console.Executable/Application.cs b/src/console/LibplanetConsole.Console.Executable/Application.cs index 81b01861..8c2baac5 100644 --- a/src/console/LibplanetConsole.Console.Executable/Application.cs +++ b/src/console/LibplanetConsole.Console.Executable/Application.cs @@ -11,6 +11,13 @@ namespace LibplanetConsole.Console.Executable; internal sealed class Application { private readonly WebApplicationBuilder _builder = WebApplication.CreateBuilder(); + private readonly LoggingFilter[] _filters = + [ + new SourceContextFilter( + "app.log", + s => s.StartsWith("LibplanetConsole.") && !s.StartsWith("LibplanetConsole.Seed.")), + new PrefixFilter("seed.log", "LibplanetConsole.Seed."), + ]; public Application(ApplicationOptions options, object[] instances) { @@ -27,7 +34,7 @@ public Application(ApplicationOptions options, object[] instances) options.ListenLocalhost(port, o => o.Protocols = HttpProtocols.Http2); }); - services.AddLogging(options.LogPath, options.LibraryLogPath); + services.AddLogging(options.LogPath, "console.log", _filters); services.AddSingleton(); services.AddSingleton(); diff --git a/src/console/LibplanetConsole.Console.Executable/ApplicationSettings.cs b/src/console/LibplanetConsole.Console.Executable/ApplicationSettings.cs index db86a112..a685af2e 100644 --- a/src/console/LibplanetConsole.Console.Executable/ApplicationSettings.cs +++ b/src/console/LibplanetConsole.Console.Executable/ApplicationSettings.cs @@ -69,14 +69,9 @@ internal sealed record class ApplicationSettings [CommandProperty] [CommandSummary("The directory path to store log.")] - [Path(Type = PathType.File, AllowEmpty = true)] + [Path(Type = PathType.Directory, AllowEmpty = true)] public string LogPath { get; set; } = string.Empty; - [CommandProperty] - [CommandSummary("The directory path to store log of the library.")] - [Path(Type = PathType.File, AllowEmpty = true)] - public string LibraryLogPath { get; set; } = string.Empty; - [CommandPropertySwitch] [CommandSummary("If set, the node and the client processes will not run.")] public bool NoProcess { get; set; } @@ -104,7 +99,6 @@ public ApplicationOptions ToOptions() return new ApplicationOptions(endPoint) { LogPath = GetFullPath(LogPath), - LibraryLogPath = GetFullPath(LibraryLogPath), Nodes = repository.Nodes, Clients = repository.Clients, Genesis = genesis, diff --git a/src/console/LibplanetConsole.Console.Executable/EntryCommands/InitializeCommand.cs b/src/console/LibplanetConsole.Console.Executable/EntryCommands/InitializeCommand.cs index 6baef585..a4676399 100644 --- a/src/console/LibplanetConsole.Console.Executable/EntryCommands/InitializeCommand.cs +++ b/src/console/LibplanetConsole.Console.Executable/EntryCommands/InitializeCommand.cs @@ -113,8 +113,7 @@ protected override void OnExecute() var repository = new Repository(endPoint, nodeOptions, clientOptions) { Genesis = genesis, - LogPath = "app.log", - LibraryLogPath = "library.log", + LogPath = "log", }; var resolver = new RepositoryPathResolver(); using var writer = new ConditionalTextWriter(Out) @@ -148,8 +147,7 @@ private NodeOptions[] GetNodeOptions(ref EndPoint? prevEndPoint) EndPoint = endPoint, PrivateKey = privateKey, StorePath = "store", - LogPath = "app.log", - LibraryLogPath = "library.log", + LogPath = "log", ActionProviderModulePath = ActionProviderModulePath, ActionProviderType = ActionProviderType, }; @@ -174,7 +172,7 @@ private ClientOptions[] GetClientOptions(ref EndPoint? prevEndPoint) { EndPoint = endPoint, PrivateKey = privateKey, - LogPath = "app.log", + LogPath = "log", }; clientOptionsList.Add(clientOptions); if (prevEndPoint is not null) diff --git a/src/console/LibplanetConsole.Console.Executable/EntryCommands/StartCommand.cs b/src/console/LibplanetConsole.Console.Executable/EntryCommands/StartCommand.cs index 3b2f73f1..f57fa8b3 100644 --- a/src/console/LibplanetConsole.Console.Executable/EntryCommands/StartCommand.cs +++ b/src/console/LibplanetConsole.Console.Executable/EntryCommands/StartCommand.cs @@ -30,7 +30,6 @@ protected override async Task OnExecuteAsync(CancellationToken cancellationToken Nodes = Repository.LoadNodeOptions(repositoryPath, resolver), Clients = Repository.LoadClientOptions(repositoryPath, resolver), LogPath = applicationSettings.LogPath, - LibraryLogPath = applicationSettings.LibraryLogPath, }; var application = new Application(applicationOptions, [.. _settingsCollection]); diff --git a/src/console/LibplanetConsole.Console.Executable/Repository.cs b/src/console/LibplanetConsole.Console.Executable/Repository.cs index 681dc247..82c6493b 100644 --- a/src/console/LibplanetConsole.Console.Executable/Repository.cs +++ b/src/console/LibplanetConsole.Console.Executable/Repository.cs @@ -35,8 +35,6 @@ public byte[] Genesis public string LogPath { get; init; } = string.Empty; - public string LibraryLogPath { get; init; } = string.Empty; - public string Source { get; private set; } = string.Empty; public static byte[] CreateGenesis(GenesisOptions genesisOptions) @@ -128,7 +126,6 @@ public dynamic Save(string repositoryPath, RepositoryPathResolver resolver) EndPoint = EndPointUtility.ToString(EndPoint), GenesisPath = PathUtility.GetRelativePath(settingsPath, genesisPath), LogPath = LogPath, - LibraryLogPath = LibraryLogPath, }; info.RepositoryPath = repositoryPath; diff --git a/src/console/LibplanetConsole.Console/ApplicationOptions.cs b/src/console/LibplanetConsole.Console/ApplicationOptions.cs index ee3eee64..06b91212 100644 --- a/src/console/LibplanetConsole.Console/ApplicationOptions.cs +++ b/src/console/LibplanetConsole.Console/ApplicationOptions.cs @@ -17,8 +17,6 @@ public ApplicationOptions(EndPoint endPoint) public string LogPath { get; init; } = string.Empty; - public string LibraryLogPath { get; init; } = string.Empty; - public bool NoProcess { get; init; } public bool Detach { get; init; } diff --git a/src/console/LibplanetConsole.Console/NodeOptions.cs b/src/console/LibplanetConsole.Console/NodeOptions.cs index 871b161b..0ab15e2c 100644 --- a/src/console/LibplanetConsole.Console/NodeOptions.cs +++ b/src/console/LibplanetConsole.Console/NodeOptions.cs @@ -16,8 +16,6 @@ public sealed record class NodeOptions public string LogPath { get; init; } = string.Empty; - public string LibraryLogPath { get; init; } = string.Empty; - public string ActionProviderModulePath { get; init; } = string.Empty; public string ActionProviderType { get; init; } = string.Empty; @@ -44,7 +42,6 @@ public static NodeOptions Load(string settingsPath) PrivateKey = new PrivateKey(applicationSettings.PrivateKey), StorePath = Path.GetFullPath(applicationSettings.StorePath, repositoryPath), LogPath = Path.GetFullPath(applicationSettings.LogPath, repositoryPath), - LibraryLogPath = Path.GetFullPath(applicationSettings.LibraryLogPath, repositoryPath), SeedEndPoint = EndPointUtility.ParseOrDefault(applicationSettings.SeedEndPoint), RepositoryPath = repositoryPath, ActionProviderModulePath = applicationSettings.ActionProviderModulePath, @@ -75,9 +72,6 @@ private sealed record class ApplicationSettings [DefaultValue("")] public string LogPath { get; init; } = string.Empty; - [DefaultValue("")] - public string LibraryLogPath { get; init; } = string.Empty; - [DefaultValue("")] public string SeedEndPoint { get; init; } = string.Empty; diff --git a/src/node/LibplanetConsole.Node.Executable/Application.cs b/src/node/LibplanetConsole.Node.Executable/Application.cs index 549b3a39..d2dfc96a 100644 --- a/src/node/LibplanetConsole.Node.Executable/Application.cs +++ b/src/node/LibplanetConsole.Node.Executable/Application.cs @@ -1,7 +1,6 @@ using JSSoft.Commands; using LibplanetConsole.Common; using LibplanetConsole.Logging; -using LibplanetConsole.Node.Evidence; using LibplanetConsole.Node.Executable.Commands; using LibplanetConsole.Node.Executable.Tracers; using LibplanetConsole.Node.Explorer; @@ -12,6 +11,14 @@ namespace LibplanetConsole.Node.Executable; internal sealed class Application { private readonly WebApplicationBuilder _builder = WebApplication.CreateBuilder(); + private readonly LoggingFilter[] _filters = + [ + new SourceContextFilter( + "app.log", + s => s.StartsWith("LibplanetConsole.") && !s.StartsWith("LibplanetConsole.Seed.")), + new PrefixFilter("seed.log", "LibplanetConsole.Seed."), + new PrefixFilter("libplanet.log", "Libplanet."), + ]; public Application(ApplicationOptions options, object[] instances) { @@ -27,7 +34,7 @@ public Application(ApplicationOptions options, object[] instances) options.ListenLocalhost(port, o => o.Protocols = HttpProtocols.Http2); }); - services.AddLogging(options.LogPath, options.LibraryLogPath); + services.AddLogging(options.LogPath, "node.log", _filters); services.AddSingleton(); services.AddSingleton(); diff --git a/src/node/LibplanetConsole.Node.Executable/ApplicationSettings.cs b/src/node/LibplanetConsole.Node.Executable/ApplicationSettings.cs index fd3069b4..7557f2ea 100644 --- a/src/node/LibplanetConsole.Node.Executable/ApplicationSettings.cs +++ b/src/node/LibplanetConsole.Node.Executable/ApplicationSettings.cs @@ -58,18 +58,11 @@ internal sealed record class ApplicationSettings public string Genesis { get; init; } = string.Empty; [CommandProperty] - [CommandSummary("Indicates the file path to save logs.")] - [Path(Type = PathType.File, AllowEmpty = true)] + [CommandSummary("Indicates the directory path to save logs.")] + [Path(Type = PathType.Directory, AllowEmpty = true)] [DefaultValue("")] public string LogPath { get; init; } = string.Empty; - [CommandProperty] - [CommandSummary("Indicates the file path to save logs for the library. " + - "If omitted, the library logs will be saved in the LogPath.")] - [Path(Type = PathType.File, AllowEmpty = true)] - [DefaultValue("")] - public string LibraryLogPath { get; init; } = string.Empty; - [CommandPropertySwitch("no-repl")] [CommandSummary("If set, the node runs without a REPL.")] [JsonIgnore] @@ -111,7 +104,6 @@ public ApplicationOptions ToOptions() SeedEndPoint = GetSeedEndPoint(), StorePath = GetFullPath(StorePath), LogPath = GetFullPath(LogPath), - LibraryLogPath = GetFullPath(LibraryLogPath), NoREPL = NoREPL, ActionProvider = actionProvider, }; diff --git a/src/node/LibplanetConsole.Node.Executable/EntryCommands/InitializeCommand.cs b/src/node/LibplanetConsole.Node.Executable/EntryCommands/InitializeCommand.cs index 7b28fae0..11590177 100644 --- a/src/node/LibplanetConsole.Node.Executable/EntryCommands/InitializeCommand.cs +++ b/src/node/LibplanetConsole.Node.Executable/EntryCommands/InitializeCommand.cs @@ -43,15 +43,9 @@ public InitializeCommand() [CommandProperty] [CommandSummary("The file path to store the application logs." + "If omitted, the 'app.log' file is used.")] - [Path(Type = PathType.File, ExistsType = PathExistsType.NotExistOrEmpty, AllowEmpty = true)] + [Path(Type = PathType.Directory, AllowEmpty = true)] public string LogPath { get; set; } = string.Empty; - [CommandProperty] - [CommandSummary("The file path to store logs other than application logs." + - "If omitted, the 'library.log' file is used.")] - [Path(Type = PathType.File, ExistsType = PathExistsType.NotExistOrEmpty, AllowEmpty = true)] - public string LibraryLogPath { get; set; } = string.Empty; - [CommandProperty] [CommandSummary("The file path of the genesis." + "If omitted, the 'genesis' file is used.")] @@ -100,8 +94,7 @@ protected override void OnExecute() var endPoint = EndPointUtility.ParseOrNext(EndPoint); var privateKey = PrivateKeyUtility.ParseOrRandom(PrivateKey); var storePath = Path.Combine(outputPath, StorePath.Fallback("store")); - var logPath = Path.Combine(outputPath, LogPath.Fallback("app.log")); - var libraryLogPath = Path.Combine(outputPath, LibraryLogPath.Fallback("library.log")); + var logPath = Path.Combine(outputPath, LogPath.Fallback("log")); var genesisPath = Path.Combine(outputPath, GenesisPath.Fallback("genesis")); var repository = new Repository { @@ -109,7 +102,6 @@ protected override void OnExecute() PrivateKey = privateKey, StorePath = storePath, LogPath = logPath, - LibraryLogPath = libraryLogPath, GenesisPath = genesisPath, SeedEndPoint = IsSingleNode is true ? endPoint : null, ActionProviderModulePath = ActionProviderModulePath, diff --git a/src/node/LibplanetConsole.Node.Executable/Repository.cs b/src/node/LibplanetConsole.Node.Executable/Repository.cs index 7effb0a8..c6a48c4f 100644 --- a/src/node/LibplanetConsole.Node.Executable/Repository.cs +++ b/src/node/LibplanetConsole.Node.Executable/Repository.cs @@ -21,8 +21,6 @@ public sealed record class Repository public string LogPath { get; init; } = string.Empty; - public string LibraryLogPath { get; init; } = string.Empty; - public string GenesisPath { get; init; } = string.Empty; public string ActionProviderModulePath { get; init; } = string.Empty; @@ -69,7 +67,6 @@ public dynamic Save(string repositoryPath) GenesisPath = GetRelativePathFromDirectory(repositoryPath, GenesisPath), StorePath = GetRelativePathFromDirectory(repositoryPath, StorePath), LogPath = GetRelativePathFromDirectory(repositoryPath, LogPath), - LibraryLogPath = GetRelativePathFromDirectory(repositoryPath, LibraryLogPath), SeedEndPoint = EndPointUtility.ToString(SeedEndPoint), ActionProviderModulePath = ActionProviderModulePath, ActionProviderType = ActionProviderType, diff --git a/src/node/LibplanetConsole.Node/ApplicationOptions.cs b/src/node/LibplanetConsole.Node/ApplicationOptions.cs index 9d932105..9115635b 100644 --- a/src/node/LibplanetConsole.Node/ApplicationOptions.cs +++ b/src/node/LibplanetConsole.Node/ApplicationOptions.cs @@ -23,8 +23,6 @@ public ApplicationOptions(EndPoint endPoint, PrivateKey privateKey, byte[] genes public string LogPath { get; set; } = string.Empty; - public string LibraryLogPath { get; set; } = string.Empty; - public bool NoREPL { get; init; } public IActionProvider? ActionProvider { get; init; } diff --git a/src/shared/LibplanetConsole.Logging/LoggingExtensions.cs b/src/shared/LibplanetConsole.Logging/LoggingExtensions.cs index 842190fd..3f1f55f1 100644 --- a/src/shared/LibplanetConsole.Logging/LoggingExtensions.cs +++ b/src/shared/LibplanetConsole.Logging/LoggingExtensions.cs @@ -1,31 +1,25 @@ using Serilog; -using Serilog.Events; namespace LibplanetConsole.Logging; -public static class LoggingExtensions +internal static class LoggingExtensions { - public const string AppSourceContext = "LibplanetConsole"; - public static IServiceCollection AddLogging( - this IServiceCollection @this, string logPath, string libraryLogPath) + this IServiceCollection @this, string logPath, string name, params LoggingFilter[] filters) { var loggerConfiguration = new LoggerConfiguration(); + var logFilename = Path.Combine(logPath, name); loggerConfiguration = loggerConfiguration.MinimumLevel.Debug(); - if (logPath != string.Empty) - { - loggerConfiguration = loggerConfiguration - .WriteTo.Logger(lc => lc - .Filter.ByIncludingOnly(e => IsApplicationLog(e)) - .WriteTo.File(logPath)); - } + loggerConfiguration = loggerConfiguration + .WriteTo.Logger(lc => lc.WriteTo.File(logFilename)); - if (libraryLogPath != string.Empty) + foreach (var filter in filters) { + var filename = Path.Combine(logPath, filter.Name); loggerConfiguration = loggerConfiguration .WriteTo.Logger(lc => lc - .Filter.ByExcluding(e => IsApplicationLog(e)) - .WriteTo.File(libraryLogPath)); + .Filter.ByIncludingOnly(filter.Filter) + .WriteTo.File(filename)); } Log.Logger = loggerConfiguration.CreateLogger(); @@ -43,24 +37,4 @@ public static IServiceCollection AddLogging( return @this; } - - private static bool IsApplicationLog(LogEvent e) - { - if (e.Properties.TryGetValue("SourceContext", out var propertyValue) is false) - { - return false; - } - - if (propertyValue is not ScalarValue scalarValue) - { - return false; - } - - if (scalarValue.Value is not string value) - { - return false; - } - - return value.StartsWith(AppSourceContext); - } } diff --git a/src/shared/LibplanetConsole.Logging/LoggingFilter.cs b/src/shared/LibplanetConsole.Logging/LoggingFilter.cs new file mode 100644 index 00000000..280a7fe8 --- /dev/null +++ b/src/shared/LibplanetConsole.Logging/LoggingFilter.cs @@ -0,0 +1,7 @@ +using Serilog.Events; + +namespace LibplanetConsole.Logging; + +internal record class LoggingFilter(string Name, Func Filter) +{ +} diff --git a/src/shared/LibplanetConsole.Logging/PrefixFilter.cs b/src/shared/LibplanetConsole.Logging/PrefixFilter.cs new file mode 100644 index 00000000..d36d4a46 --- /dev/null +++ b/src/shared/LibplanetConsole.Logging/PrefixFilter.cs @@ -0,0 +1,6 @@ +namespace LibplanetConsole.Logging; + +internal record class PrefixFilter(string Name, string Prefix) + : SourceContextFilter(Name, e => e.StartsWith(Prefix)) +{ +} diff --git a/src/shared/LibplanetConsole.Logging/SourceContextFilter.cs b/src/shared/LibplanetConsole.Logging/SourceContextFilter.cs new file mode 100644 index 00000000..c4bfa73d --- /dev/null +++ b/src/shared/LibplanetConsole.Logging/SourceContextFilter.cs @@ -0,0 +1,27 @@ +using Serilog.Events; + +namespace LibplanetConsole.Logging; + +internal record class SourceContextFilter(string Name, Func Predicate) + : LoggingFilter(Name, e => Test(e, Predicate)) +{ + private static bool Test(LogEvent e, Func predicate) + { + if (e.Properties.TryGetValue("SourceContext", out var propertyValue) is false) + { + return false; + } + + if (propertyValue is not ScalarValue scalarValue) + { + return false; + } + + if (scalarValue.Value is not string value) + { + return false; + } + + return predicate(value); + } +} From 0b15963e58e30abcd28e1151f2bde96c24a68318 Mon Sep 17 00:00:00 2001 From: s2quake Date: Sun, 13 Oct 2024 14:21:13 +0900 Subject: [PATCH 12/18] refactor: Done --- .editorconfig | 7 + .../Application.cs | 5 +- .../ApplicationSettings.cs | 2 +- .../EntryCommands/InitializeCommand.cs | 5 +- .../SystemTerminal.cs | 79 +++++++- .../Tracers/BlockChainEventTracer.cs | 9 +- .../Client.BlockChain.cs | 5 + src/client/LibplanetConsole.Client/Client.cs | 30 ++- .../Commands/TxCommand.cs | 1 + .../Grpc/BlockChainGrpcServiceV1.cs | 2 +- src/client/LibplanetConsole.Client/IClient.cs | 2 - .../ServiceCollectionExtensions.cs | 1 + .../Commands/EvidenceCommand.cs | 8 +- .../Evidence.cs | 65 +++++-- .../Grpc/EvidenceChannel.cs | 24 +++ .../ServiceCollectionExtensions.cs | 7 +- .../Application.cs | 8 +- .../SystemTerminal.cs | 79 +++++++- .../Tracers/NodeCollectionEventTracer.cs | 17 +- .../LibplanetConsole.Console/BlockChain.cs | 183 ++++++++++++++++++ .../Client.BlockChain.cs | 2 + .../LibplanetConsole.Console/Client.cs | 16 +- .../ClientContentBase.cs | 69 +++---- .../LibplanetConsole.Console/ClientFactory.cs | 8 +- .../Commands/ClientCommand.cs | 1 + .../Commands/NodeCommand.cs | 1 + .../Commands/TxCommand.cs | 1 + .../LibplanetConsole.Console/IBlockChain.cs | 32 --- .../LibplanetConsole.Console/IClient.cs | 2 + .../IClientContent.cs | 6 +- src/console/LibplanetConsole.Console/INode.cs | 5 +- .../LibplanetConsole.Console/INodeContent.cs | 6 +- .../Node.BlockChain.cs | 2 + src/console/LibplanetConsole.Console/Node.cs | 39 +++- .../NodeContentBase.cs | 69 +++---- .../LibplanetConsole.Console/NodeFactory.cs | 8 +- .../ServiceCollectionExtensions.cs | 68 ++++--- .../EvidenceServiceGrpcV1.cs | 6 +- .../NodeEndpointRouteBuilderExtensions.cs | 15 ++ .../ServiceCollectionExtensions.cs | 1 - .../Application.cs | 8 +- .../EntryCommands/InitializeCommand.cs | 3 +- .../SystemTerminal.cs | 79 +++++++- .../Tracers/BlockChainEventTracer.cs | 9 +- .../Commands/TxCommand.cs | 1 + .../Grpc/BlockChainGrpcServiceV1.cs | 2 +- .../Grpc/NodeGrpcServiceV1.cs | 12 +- src/node/LibplanetConsole.Node/IBlockChain.cs | 32 --- .../LibplanetConsole.Node.csproj | 10 +- .../LibplanetConsole.Node/Node.BlockChain.cs | 5 + src/node/LibplanetConsole.Node/Node.cs | 8 +- .../ServiceCollectionExtensions.cs | 1 + .../BlockInfo.Node.cs | 2 +- .../LibplanetConsole.Blockchain/BlockInfo.cs | 5 + .../Grpc/BlockChainService.cs | 4 +- .../IBlockChain.cs | 13 +- .../LibplanetConsole.Client/ClientInfo.cs | 15 +- .../Protos/ClientGrpcService.proto | 4 +- .../LibplanetConsole.Evidence/EvidenceInfo.cs | 8 +- .../Protos/EvidenceGrpcService.proto | 6 +- .../ConnectionMonitor.cs | 2 +- .../LibplanetConsole.Grpc/EventStreamer.cs | 8 +- src/shared/LibplanetConsole.Grpc/Streamer.cs | 2 +- .../LibplanetConsole.Node/Grpc/NodeService.cs | 67 ++++++- src/shared/LibplanetConsole.Node/NodeInfo.cs | 15 +- .../Protos/NodeGrpcService.proto | 4 +- 66 files changed, 904 insertions(+), 317 deletions(-) create mode 100644 src/console/LibplanetConsole.Console.Evidence/Grpc/EvidenceChannel.cs create mode 100644 src/console/LibplanetConsole.Console/BlockChain.cs delete mode 100644 src/console/LibplanetConsole.Console/IBlockChain.cs rename src/node/LibplanetConsole.Node.Evidence/{Services => Grpc}/EvidenceServiceGrpcV1.cs (86%) create mode 100644 src/node/LibplanetConsole.Node.Evidence/NodeEndpointRouteBuilderExtensions.cs delete mode 100644 src/node/LibplanetConsole.Node/IBlockChain.cs rename src/{client/LibplanetConsole.Client => shared/LibplanetConsole.Blockchain}/IBlockChain.cs (84%) diff --git a/.editorconfig b/.editorconfig index 3beec024..7b3f1984 100644 --- a/.editorconfig +++ b/.editorconfig @@ -83,6 +83,13 @@ dotnet_diagnostic.MEN007.severity = none # MEN016: Avoid top-level statements dotnet_diagnostic.MEN016.severity = none +[**/obj/**/*.cs] +# SA1200: Using directives should be placed correctly +dotnet_diagnostic.SA1200.severity = none + +# CS8981: The type name only contains lower-cased ascii characters +dotnet_diagnostic.CS8981.severity = none + [*.csproj] quote_type = double diff --git a/src/client/LibplanetConsole.Client.Executable/Application.cs b/src/client/LibplanetConsole.Client.Executable/Application.cs index 22a34c3b..7c22a225 100644 --- a/src/client/LibplanetConsole.Client.Executable/Application.cs +++ b/src/client/LibplanetConsole.Client.Executable/Application.cs @@ -29,7 +29,10 @@ public Application(ApplicationOptions options, object[] instances) options.ListenLocalhost(port, o => o.Protocols = HttpProtocols.Http2); }); - services.AddLogging(options.LogPath, "client.log", _filters); + if (options.LogPath != string.Empty) + { + services.AddLogging(options.LogPath, "client.log", _filters); + } services.AddSingleton(); services.AddSingleton(); diff --git a/src/client/LibplanetConsole.Client.Executable/ApplicationSettings.cs b/src/client/LibplanetConsole.Client.Executable/ApplicationSettings.cs index 0fc6be43..aff7d370 100644 --- a/src/client/LibplanetConsole.Client.Executable/ApplicationSettings.cs +++ b/src/client/LibplanetConsole.Client.Executable/ApplicationSettings.cs @@ -36,7 +36,7 @@ internal sealed record class ApplicationSettings [CommandProperty] [CommandSummary("Indicates the file path to save logs.")] - [Path(Type = PathType.File, AllowEmpty = true)] + [Path(Type = PathType.Directory, AllowEmpty = true)] [DefaultValue("")] public string LogPath { get; set; } = string.Empty; diff --git a/src/client/LibplanetConsole.Client.Executable/EntryCommands/InitializeCommand.cs b/src/client/LibplanetConsole.Client.Executable/EntryCommands/InitializeCommand.cs index 541ae4d8..c34affca 100644 --- a/src/client/LibplanetConsole.Client.Executable/EntryCommands/InitializeCommand.cs +++ b/src/client/LibplanetConsole.Client.Executable/EntryCommands/InitializeCommand.cs @@ -33,9 +33,8 @@ public InitializeCommand() public string EndPoint { get; set; } = string.Empty; [CommandProperty] - [CommandSummary("The file path to store the application logs." + - "If omitted, the 'app.log' file is used.")] - [Path(Type = PathType.File, ExistsType = PathExistsType.NotExistOrEmpty, AllowEmpty = true)] + [CommandSummary("Indicates the file path to save logs.")] + [Path(Type = PathType.Directory, AllowEmpty = true)] public string LogPath { get; set; } = string.Empty; [CommandPropertySwitch("quiet", 'q')] diff --git a/src/client/LibplanetConsole.Client.Executable/SystemTerminal.cs b/src/client/LibplanetConsole.Client.Executable/SystemTerminal.cs index 190e7bbb..4ce6c41e 100644 --- a/src/client/LibplanetConsole.Client.Executable/SystemTerminal.cs +++ b/src/client/LibplanetConsole.Client.Executable/SystemTerminal.cs @@ -1,22 +1,67 @@ +using System.Text; using JSSoft.Commands.Extensions; using JSSoft.Terminals; +using LibplanetConsole.Blockchain; +using LibplanetConsole.Common.Extensions; namespace LibplanetConsole.Client.Executable; internal sealed class SystemTerminal : SystemTerminalBase { + private const string PromptText = "libplanet-client $ "; + private readonly SynchronizationContext _synchronizationContext; private readonly CommandContext _commandContext; + private readonly IBlockChain _blockChain; + private BlockInfo _tip; public SystemTerminal( - IHostApplicationLifetime applicationLifetime, CommandContext commandContext) + IHostApplicationLifetime applicationLifetime, + CommandContext commandContext, + IBlockChain blockChain, + SynchronizationContext synchronizationContext) { + _synchronizationContext = synchronizationContext; _commandContext = commandContext; _commandContext.Owner = applicationLifetime; - Prompt = "libplanet-client $ "; + _blockChain = blockChain; + _blockChain.BlockAppended += BlockChain_BlockAppended; + _blockChain.Started += BlockChain_Started; + _blockChain.Stopped += BlockChain_Stopped; + UpdatePrompt(_blockChain.Tip); applicationLifetime.ApplicationStopping.Register(() => Prompt = "\u001b0"); } - protected override string FormatPrompt(string prompt) => prompt; + protected override void OnDispose() + { + _blockChain.Started -= BlockChain_Started; + _blockChain.Stopped -= BlockChain_Stopped; + _blockChain.BlockAppended -= BlockChain_BlockAppended; + base.OnDispose(); + } + + protected override string FormatPrompt(string prompt) + { + var tip = _tip; + if (_tip.Height == -1) + { + return prompt; + } + else + { + var tsb = new TerminalStringBuilder(); + tsb.AppendEnd(); + tsb.Append($"#{tip.Height} "); + tsb.Foreground = TerminalColorType.BrightGreen; + tsb.Append($"{tip.Hash.ToShortString()} "); + tsb.ResetOptions(); + tsb.Append($"by "); + tsb.Foreground = TerminalColorType.BrightGreen; + tsb.Append($"{tip.Miner.ToShortString()}"); + tsb.ResetOptions(); + tsb.AppendEnd(); + return $"[{tsb}] {PromptText}"; + } + } protected override string[] GetCompletion(string[] items, string find) => _commandContext.GetCompletion(items, find); @@ -29,4 +74,32 @@ protected override void OnInitialize(TextWriter @out, TextWriter error) _commandContext.Out = @out; _commandContext.Error = error; } + + private void BlockChain_BlockAppended(object? sender, BlockEventArgs e) + => _synchronizationContext.Post(_ => UpdatePrompt(e.BlockInfo), null); + + private void BlockChain_Started(object? sender, EventArgs e) + => _synchronizationContext.Post(_ => UpdatePrompt(_blockChain.Tip), null); + + private void BlockChain_Stopped(object? sender, EventArgs e) + => _synchronizationContext.Post(_ => UpdatePrompt(_blockChain.Tip), null); + + private void UpdatePrompt(BlockInfo tip) + { + if (tip.Height == -1) + { + Prompt = PromptText; + } + else + { + var sb = new StringBuilder(); + sb.Append($"#{tip.Height} "); + sb.Append($"{tip.Hash.ToShortString()} "); + sb.Append($"by "); + sb.Append($"{tip.Miner.ToShortString()}"); + Prompt = $"[{sb}] {PromptText}"; + } + + _tip = tip; + } } diff --git a/src/client/LibplanetConsole.Client.Executable/Tracers/BlockChainEventTracer.cs b/src/client/LibplanetConsole.Client.Executable/Tracers/BlockChainEventTracer.cs index 377a9885..4bf1ef64 100644 --- a/src/client/LibplanetConsole.Client.Executable/Tracers/BlockChainEventTracer.cs +++ b/src/client/LibplanetConsole.Client.Executable/Tracers/BlockChainEventTracer.cs @@ -33,9 +33,10 @@ private void Client_BlockAppended(object? sender, BlockEventArgs e) var blockInfo = e.BlockInfo; var hash = blockInfo.Hash; var miner = blockInfo.Miner; - var message = $"Block #{blockInfo.Height} '{hash.ToShortString()}' " + - $"Appended by '{miner.ToShortString()}'"; - Console.Out.WriteColoredLine(message, TerminalColorType.BrightGreen); - _logger.LogInformation(message); + _logger.LogInformation( + "Block #{TipHeight} '{TipHash}' Appended by '{TipMiner}'", + blockInfo.Height, + hash.ToShortString(), + miner.ToShortString()); } } diff --git a/src/client/LibplanetConsole.Client/Client.BlockChain.cs b/src/client/LibplanetConsole.Client/Client.BlockChain.cs index a3740e12..d13404d4 100644 --- a/src/client/LibplanetConsole.Client/Client.BlockChain.cs +++ b/src/client/LibplanetConsole.Client/Client.BlockChain.cs @@ -1,5 +1,6 @@ using System.Security.Cryptography; using Grpc.Core; +using LibplanetConsole.Blockchain; using LibplanetConsole.Blockchain.Grpc; using LibplanetConsole.Node; @@ -9,6 +10,10 @@ internal sealed partial class Client : IBlockChain { private static readonly Codec _codec = new(); + public event EventHandler? BlockAppended; + + public BlockInfo Tip => Info.Tip; + public async Task SendTransactionAsync( IAction[] actions, CancellationToken cancellationToken) { diff --git a/src/client/LibplanetConsole.Client/Client.cs b/src/client/LibplanetConsole.Client/Client.cs index e1fe082f..e55bf3c6 100644 --- a/src/client/LibplanetConsole.Client/Client.cs +++ b/src/client/LibplanetConsole.Client/Client.cs @@ -1,3 +1,4 @@ +using Grpc.Core; using Grpc.Net.Client; using LibplanetConsole.Blockchain; using LibplanetConsole.Blockchain.Grpc; @@ -31,8 +32,6 @@ public Client(ILogger logger, ApplicationOptions options) _logger.LogDebug("Client is created: {Address}", Address); } - public event EventHandler? BlockAppended; - public event EventHandler? Started; public event EventHandler? Stopped; @@ -85,11 +84,11 @@ public async Task StartAsync(CancellationToken cancellationToken) var blockChainService = new BlockChainService(channel); nodeService.Started += (sender, e) => InvokeNodeStartedEvent(e); nodeService.Stopped += (sender, e) => InvokeNodeStoppedEvent(); - blockChainService.BlockAppended += (sender, e) => InvokeBlockAppendedEvent(e); + blockChainService.BlockAppended += BlockChainService_BlockAppended; try { - await nodeService.StartAsync(cancellationToken); - await blockChainService.StartAsync(cancellationToken); + await nodeService.InitializeAsync(cancellationToken); + await blockChainService.InitializeAsync(cancellationToken); } catch { @@ -102,7 +101,11 @@ public async Task StartAsync(CancellationToken cancellationToken) _channel = channel; _nodeService = nodeService; _blockChainService = blockChainService; - _info = _info with { NodeAddress = NodeInfo.Address }; + _info = _info with + { + NodeAddress = NodeInfo.Address, + Tip = nodeService.Info.Tip, + }; IsRunning = true; _logger.LogDebug( "Client is started: {Address} -> {NodeAddress}", Address, NodeInfo.Address); @@ -124,13 +127,13 @@ public async Task StopAsync(CancellationToken cancellationToken) if (_nodeService is not null) { - await _nodeService.StopAsync(cancellationToken); + await _nodeService.ReleaseAsync(cancellationToken); _nodeService = null; } if (_blockChainService is not null) { - await _blockChainService.StopAsync(cancellationToken); + await _blockChainService.ReleaseAsync(cancellationToken); _blockChainService = null; } @@ -140,7 +143,7 @@ public async Task StopAsync(CancellationToken cancellationToken) _blockChainService = null; _nodeService = null; IsRunning = false; - _info = _info with { NodeAddress = default }; + _info = ClientInfo.Empty; _logger.LogDebug("Client is stopped: {Address}", Address); Stopped?.Invoke(this, EventArgs.Empty); } @@ -186,4 +189,13 @@ private void NodeService_Disconnected(object? sender, EventArgs e) Stopped?.Invoke(this, EventArgs.Empty); } } + + private void BlockChainService_BlockAppended(object? sender, BlockEventArgs e) + { + _info = _info with + { + Tip = e.BlockInfo, + }; + BlockAppended?.Invoke(this, e); + } } diff --git a/src/client/LibplanetConsole.Client/Commands/TxCommand.cs b/src/client/LibplanetConsole.Client/Commands/TxCommand.cs index 841a2800..6f8f34fd 100644 --- a/src/client/LibplanetConsole.Client/Commands/TxCommand.cs +++ b/src/client/LibplanetConsole.Client/Commands/TxCommand.cs @@ -1,4 +1,5 @@ using JSSoft.Commands; +using LibplanetConsole.Blockchain; using LibplanetConsole.Common.Actions; using LibplanetConsole.Common.Extensions; diff --git a/src/client/LibplanetConsole.Client/Grpc/BlockChainGrpcServiceV1.cs b/src/client/LibplanetConsole.Client/Grpc/BlockChainGrpcServiceV1.cs index dcd345ff..faf417c7 100644 --- a/src/client/LibplanetConsole.Client/Grpc/BlockChainGrpcServiceV1.cs +++ b/src/client/LibplanetConsole.Client/Grpc/BlockChainGrpcServiceV1.cs @@ -33,7 +33,7 @@ public async override Task GetNextNonce( public override async Task GetTipHash( GetTipHashRequest request, ServerCallContext context) { - var blockHash = await blockChain.GetTipHashAsync(context.CancellationToken); + var blockHash = await Task.FromResult(blockChain.Tip.Hash); return new GetTipHashResponse { BlockHash = blockHash.ToString() }; } diff --git a/src/client/LibplanetConsole.Client/IClient.cs b/src/client/LibplanetConsole.Client/IClient.cs index 0436d3b9..478e740a 100644 --- a/src/client/LibplanetConsole.Client/IClient.cs +++ b/src/client/LibplanetConsole.Client/IClient.cs @@ -24,6 +24,4 @@ public interface IClient : IVerifier Task StartAsync(CancellationToken cancellationToken); Task StopAsync(CancellationToken cancellationToken); - - Task SendTransactionAsync(IAction[] actions, CancellationToken cancellationToken); } diff --git a/src/client/LibplanetConsole.Client/ServiceCollectionExtensions.cs b/src/client/LibplanetConsole.Client/ServiceCollectionExtensions.cs index fe2f04f4..043edd80 100644 --- a/src/client/LibplanetConsole.Client/ServiceCollectionExtensions.cs +++ b/src/client/LibplanetConsole.Client/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ using JSSoft.Commands; +using LibplanetConsole.Blockchain; using LibplanetConsole.Client.Commands; using LibplanetConsole.Common; using Microsoft.Extensions.DependencyInjection; diff --git a/src/console/LibplanetConsole.Console.Evidence/Commands/EvidenceCommand.cs b/src/console/LibplanetConsole.Console.Evidence/Commands/EvidenceCommand.cs index cde30c51..c8bc52ed 100644 --- a/src/console/LibplanetConsole.Console.Evidence/Commands/EvidenceCommand.cs +++ b/src/console/LibplanetConsole.Console.Evidence/Commands/EvidenceCommand.cs @@ -15,7 +15,7 @@ public async Task NewAsync( string nodeAddress = "", CancellationToken cancellationToken = default) { var node = nodes.Current ?? throw new InvalidOperationException("No node is selected."); - var evidence = node.GetRequiredService(); + var evidence = node.GetRequiredKeyedService(INode.Key); var evidenceInfo = await evidence.AddEvidenceAsync(cancellationToken); await Out.WriteLineAsJsonAsync(evidenceInfo); } @@ -25,7 +25,7 @@ public async Task RaiseAsync( CancellationToken cancellationToken = default) { var node = nodes.Current ?? throw new InvalidOperationException("No node is selected."); - var evidence = node.GetRequiredService(); + var evidence = node.GetRequiredKeyedService(INode.Key); await evidence.ViolateAsync(cancellationToken); } @@ -33,7 +33,7 @@ public async Task RaiseAsync( public async Task ListAsync(long height = -1, CancellationToken cancellationToken = default) { var node = nodes.Current ?? throw new InvalidOperationException("No node is selected."); - var evidence = node.GetRequiredService(); + var evidence = node.GetRequiredKeyedService(INode.Key); var evidenceInfos = await evidence.GetEvidenceAsync(height, cancellationToken); await Out.WriteLineAsJsonAsync(evidenceInfos); } @@ -44,7 +44,7 @@ public async Task UnjailAsync( CancellationToken cancellationToken = default) { var node = nodes.Current ?? throw new InvalidOperationException("No node is selected."); - var evidence = node.GetService(); + var evidence = node.GetRequiredKeyedService(INode.Key); await evidence.UnjailAsync(cancellationToken); } #endif // LIBPLANET_DPOS diff --git a/src/console/LibplanetConsole.Console.Evidence/Evidence.cs b/src/console/LibplanetConsole.Console.Evidence/Evidence.cs index 7c861e7d..66c548a7 100644 --- a/src/console/LibplanetConsole.Console.Evidence/Evidence.cs +++ b/src/console/LibplanetConsole.Console.Evidence/Evidence.cs @@ -1,37 +1,68 @@ +using Grpc.Core; +using Grpc.Net.Client; using LibplanetConsole.Evidence; +using LibplanetConsole.Evidence.Grpc; +using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Console.Evidence; -internal sealed class Evidence(INode node) - : INodeContent, IEvidence - // , INodeContentService +internal sealed class Evidence([FromKeyedServices(INode.Key)] INode node) + : NodeContentBase("evidence"), IEvidence { - // private readonly RemoteService _evidenceService = new(); + private GrpcChannel? _channel; + private EvidenceGrpcService.EvidenceGrpcServiceClient? _client; - INode INodeContent.Node => node; + public async Task AddEvidenceAsync(CancellationToken cancellationToken) + { + if (_client is null) + { + throw new InvalidOperationException("The channel is not initialized."); + } - string INodeContent.Name => "evidence"; + var request = new AddEvidenceRequest(); + var callOptions = new CallOptions(cancellationToken: cancellationToken); + var response = await _client.AddEvidenceAsync(request, callOptions); + return response.EvidenceInformation; + } - // IRemoteService INodeContentService.RemoteService => _evidenceService; + public async Task GetEvidenceAsync( + long height, CancellationToken cancellationToken) + { + if (_client is null) + { + throw new InvalidOperationException("The channel is not initialized."); + } - // private IEvidenceService Service => _evidenceService.Service; + var request = new GetEvidenceRequest { Height = height }; + var callOptions = new CallOptions(cancellationToken: cancellationToken); + var response = await _client.GetEvidenceAsync(request, callOptions); + return [.. response.EvidenceInformations.Select(item => (EvidenceInfo)item)]; + } - public Task AddEvidenceAsync(CancellationToken cancellationToken) + public async Task ViolateAsync(CancellationToken cancellationToken) { - // return Service.AddEvidenceAsync(cancellationToken); - throw new NotImplementedException(); + if (_client is null) + { + throw new InvalidOperationException("The channel is not initialized."); + } + + var request = new ViolateRequest(); + var callOptions = new CallOptions(cancellationToken: cancellationToken); + await _client.ViolateAsync(request, callOptions); } - public Task GetEvidenceAsync(long height, CancellationToken cancellationToken) + protected override async Task OnStartAsync(CancellationToken cancellationToken) { - // return Service.GetEvidenceAsync(height, cancellationToken); - throw new NotImplementedException(); + _channel = EvidenceChannel.CreateChannel(node.EndPoint); + _client = new EvidenceGrpcService.EvidenceGrpcServiceClient(_channel); + await Task.CompletedTask; } - public Task ViolateAsync(CancellationToken cancellationToken) + protected override async Task OnStopAsync(CancellationToken cancellationToken) { - // return Service.ViolateAsync(cancellationToken); - throw new NotImplementedException(); + _channel?.Dispose(); + _channel = null; + await Task.CompletedTask; } #if LIBPLANET_DPOS diff --git a/src/console/LibplanetConsole.Console.Evidence/Grpc/EvidenceChannel.cs b/src/console/LibplanetConsole.Console.Evidence/Grpc/EvidenceChannel.cs new file mode 100644 index 00000000..3ccfdf7c --- /dev/null +++ b/src/console/LibplanetConsole.Console.Evidence/Grpc/EvidenceChannel.cs @@ -0,0 +1,24 @@ +using Grpc.Core; +using Grpc.Net.Client; +using Grpc.Net.Client.Configuration; +using LibplanetConsole.Common; + +namespace LibplanetConsole.Evidence.Grpc; + +internal static class EvidenceChannel +{ + private static readonly GrpcChannelOptions _channelOptions = new() + { + ThrowOperationCanceledOnCancellation = true, + MaxRetryAttempts = 10, + ServiceConfig = new() + { + }, + }; + + public static GrpcChannel CreateChannel(EndPoint endPoint) + { + var address = $"http://{EndPointUtility.ToString(endPoint)}"; + return GrpcChannel.ForAddress(address, _channelOptions); + } +} diff --git a/src/console/LibplanetConsole.Console.Evidence/ServiceCollectionExtensions.cs b/src/console/LibplanetConsole.Console.Evidence/ServiceCollectionExtensions.cs index 24886676..365377d7 100644 --- a/src/console/LibplanetConsole.Console.Evidence/ServiceCollectionExtensions.cs +++ b/src/console/LibplanetConsole.Console.Evidence/ServiceCollectionExtensions.cs @@ -8,8 +8,11 @@ public static class ServiceCollectionExtensions { public static IServiceCollection AddEvidence(this IServiceCollection @this) { - @this.AddScoped() - .AddScoped(s => s.GetRequiredService()); + @this.AddKeyedScoped(INode.Key) + .AddKeyedScoped( + INode.Key, (s, k) => s.GetRequiredKeyedService(k)) + .AddKeyedScoped( + INode.Key, (s, k) => s.GetRequiredKeyedService(k)); @this.AddSingleton(); diff --git a/src/console/LibplanetConsole.Console.Executable/Application.cs b/src/console/LibplanetConsole.Console.Executable/Application.cs index 8c2baac5..ed5270c0 100644 --- a/src/console/LibplanetConsole.Console.Executable/Application.cs +++ b/src/console/LibplanetConsole.Console.Executable/Application.cs @@ -34,7 +34,11 @@ public Application(ApplicationOptions options, object[] instances) options.ListenLocalhost(port, o => o.Protocols = HttpProtocols.Http2); }); - services.AddLogging(options.LogPath, "console.log", _filters); + if (options.LogPath != string.Empty) + { + services.AddLogging(options.LogPath, "console.log", _filters); + } + services.AddSingleton(); services.AddSingleton(); @@ -43,8 +47,8 @@ public Application(ApplicationOptions options, object[] instances) services.AddSingleton() .AddSingleton(s => s.GetRequiredService()); - services.AddEvidence(); services.AddConsole(options); + services.AddEvidence(); services.AddGrpc(); services.AddGrpcReflection(); diff --git a/src/console/LibplanetConsole.Console.Executable/SystemTerminal.cs b/src/console/LibplanetConsole.Console.Executable/SystemTerminal.cs index 98f2e363..66a38cad 100644 --- a/src/console/LibplanetConsole.Console.Executable/SystemTerminal.cs +++ b/src/console/LibplanetConsole.Console.Executable/SystemTerminal.cs @@ -1,22 +1,67 @@ +using System.Text; using JSSoft.Commands.Extensions; using JSSoft.Terminals; +using LibplanetConsole.Blockchain; +using LibplanetConsole.Common.Extensions; namespace LibplanetConsole.Console.Executable; internal sealed class SystemTerminal : SystemTerminalBase { + private const string PromptText = "libplanet-console $ "; + private readonly SynchronizationContext _synchronizationContext; private readonly CommandContext _commandContext; + private readonly IBlockChain _blockChain; + private BlockInfo _tip; public SystemTerminal( - IHostApplicationLifetime applicationLifetime, CommandContext commandContext) + IHostApplicationLifetime applicationLifetime, + CommandContext commandContext, + IBlockChain blockChain, + SynchronizationContext synchronizationContext) { + _synchronizationContext = synchronizationContext; _commandContext = commandContext; _commandContext.Owner = applicationLifetime; - Prompt = "libplanet-console $ "; + _blockChain = blockChain; + _blockChain.BlockAppended += BlockChain_BlockAppended; + _blockChain.Started += BlockChain_Started; + _blockChain.Stopped += BlockChain_Stopped; + UpdatePrompt(_blockChain.Tip); applicationLifetime.ApplicationStopping.Register(() => Prompt = "\u001b0"); } - protected override string FormatPrompt(string prompt) => prompt; + protected override void OnDispose() + { + _blockChain.Started -= BlockChain_Started; + _blockChain.Stopped -= BlockChain_Stopped; + _blockChain.BlockAppended -= BlockChain_BlockAppended; + base.OnDispose(); + } + + protected override string FormatPrompt(string prompt) + { + var tip = _tip; + if (_tip.Height == -1) + { + return prompt; + } + else + { + var tsb = new TerminalStringBuilder(); + tsb.AppendEnd(); + tsb.Append($"#{tip.Height} "); + tsb.Foreground = TerminalColorType.BrightGreen; + tsb.Append($"{tip.Hash.ToShortString()} "); + tsb.ResetOptions(); + tsb.Append($"by "); + tsb.Foreground = TerminalColorType.BrightGreen; + tsb.Append($"{tip.Miner.ToShortString()}"); + tsb.ResetOptions(); + tsb.AppendEnd(); + return $"[{tsb}] {PromptText}"; + } + } protected override string[] GetCompletion(string[] items, string find) => _commandContext.GetCompletion(items, find); @@ -29,4 +74,32 @@ protected override void OnInitialize(TextWriter @out, TextWriter error) _commandContext.Out = @out; _commandContext.Error = error; } + + private void BlockChain_BlockAppended(object? sender, BlockEventArgs e) + => _synchronizationContext.Post(_ => UpdatePrompt(e.BlockInfo), null); + + private void BlockChain_Started(object? sender, EventArgs e) + => _synchronizationContext.Post(_ => UpdatePrompt(_blockChain.Tip), null); + + private void BlockChain_Stopped(object? sender, EventArgs e) + => _synchronizationContext.Post(_ => UpdatePrompt(_blockChain.Tip), null); + + private void UpdatePrompt(BlockInfo tip) + { + if (tip.Height == -1) + { + Prompt = PromptText; + } + else + { + var sb = new StringBuilder(); + sb.Append($"#{tip.Height} "); + sb.Append($"{tip.Hash.ToShortString()} "); + sb.Append($"by "); + sb.Append($"{tip.Miner.ToShortString()}"); + Prompt = $"[{sb}] {PromptText}"; + } + + _tip = tip; + } } diff --git a/src/console/LibplanetConsole.Console.Executable/Tracers/NodeCollectionEventTracer.cs b/src/console/LibplanetConsole.Console.Executable/Tracers/NodeCollectionEventTracer.cs index b1cfd005..f3390aa9 100644 --- a/src/console/LibplanetConsole.Console.Executable/Tracers/NodeCollectionEventTracer.cs +++ b/src/console/LibplanetConsole.Console.Executable/Tracers/NodeCollectionEventTracer.cs @@ -8,11 +8,14 @@ namespace LibplanetConsole.Console.Executable.Tracers; internal sealed class NodeCollectionEventTracer : IHostedService, IDisposable { private readonly INodeCollection _nodes; + private readonly ILogger _logger; private INode? _current; - public NodeCollectionEventTracer(INodeCollection nodes) + public NodeCollectionEventTracer( + INodeCollection nodes, ILogger logger) { _nodes = nodes; + _logger = logger; UpdateCurrent(_nodes.Current); foreach (var node in _nodes) { @@ -42,14 +45,14 @@ void IDisposable.Dispose() private void UpdateCurrent(INode? node) { - if (_current?.GetService(typeof(IBlockChain)) is IBlockChain blockChain1) + if (_current?.GetKeyedService(INode.Key) is IBlockChain blockChain1) { blockChain1.BlockAppended -= BlockChain_BlockAppended; } _current = node; - if (_current?.GetService(typeof(IBlockChain)) is IBlockChain blockChain2) + if (_current?.GetKeyedService(INode.Key) is IBlockChain blockChain2) { blockChain2.BlockAppended += BlockChain_BlockAppended; } @@ -105,9 +108,11 @@ private void BlockChain_BlockAppended(object? sender, BlockEventArgs e) var blockInfo = e.BlockInfo; var hash = blockInfo.Hash; var miner = blockInfo.Miner; - var message = $"Block #{blockInfo.Height} '{hash.ToShortString()}' " + - $"Appended by '{miner.ToShortString()}'"; - System.Console.Out.WriteColoredLine(message, TerminalColorType.BrightBlue); + _logger.LogInformation( + "Block #{TipHeight} '{TipHash}' Appended by '{TipMiner}'", + blockInfo.Height, + hash.ToShortString(), + miner.ToShortString()); } private void Node_Attached(object? sender, EventArgs e) diff --git a/src/console/LibplanetConsole.Console/BlockChain.cs b/src/console/LibplanetConsole.Console/BlockChain.cs new file mode 100644 index 00000000..8d95042a --- /dev/null +++ b/src/console/LibplanetConsole.Console/BlockChain.cs @@ -0,0 +1,183 @@ +using System.Diagnostics; +using System.Security.Cryptography; +using LibplanetConsole.Blockchain; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace LibplanetConsole.Console; + +internal sealed class BlockChain : IBlockChain, IDisposable +{ + private readonly INodeCollection _nodes; + private readonly ILogger _logger; + private IBlockChain? _blockChain; + private bool _isDisposed; + + public BlockChain(NodeCollection nodes, ILogger logger) + { + _nodes = nodes; + _logger = logger; + UpdateCurrent(_nodes.Current); + _nodes.CurrentChanged += Nodes_CurrentChanged; + } + + public event EventHandler? BlockAppended; + + public event EventHandler? Started; + + public event EventHandler? Stopped; + + public BlockInfo Tip { get; private set; } = BlockInfo.Empty; + + public bool IsRunning { get; private set; } + + void IDisposable.Dispose() + { + if (_isDisposed is false) + { + _nodes.CurrentChanged -= Nodes_CurrentChanged; + UpdateCurrent(null); + + _isDisposed = true; + } + } + + Task IBlockChain.SendTransactionAsync( + IAction[] actions, CancellationToken cancellationToken) + { + if (IsRunning is false || _blockChain is null) + { + throw new InvalidOperationException("BlockChain is not running."); + } + + return _blockChain.SendTransactionAsync(actions, cancellationToken); + } + + Task IBlockChain.GetNextNonceAsync( + Address address, CancellationToken cancellationToken) + { + if (IsRunning is false || _blockChain is null) + { + throw new InvalidOperationException("BlockChain is not running."); + } + + return _blockChain.GetNextNonceAsync(address, cancellationToken); + } + + Task IBlockChain.GetStateAsync( + BlockHash? blockHash, + Address accountAddress, + Address address, + CancellationToken cancellationToken) + { + if (IsRunning is false || _blockChain is null) + { + throw new InvalidOperationException("BlockChain is not running."); + } + + return _blockChain.GetStateAsync(blockHash, accountAddress, address, cancellationToken); + } + + Task IBlockChain.GetStateByStateRootHashAsync( + HashDigest stateRootHash, + Address accountAddress, + Address address, + CancellationToken cancellationToken) + { + if (IsRunning is false || _blockChain is null) + { + throw new InvalidOperationException("BlockChain is not running."); + } + + return _blockChain.GetStateByStateRootHashAsync( + stateRootHash, accountAddress, address, cancellationToken); + } + + Task IBlockChain.GetBlockHashAsync( + long height, CancellationToken cancellationToken) + { + if (IsRunning is false || _blockChain is null) + { + throw new InvalidOperationException("BlockChain is not running."); + } + + return _blockChain.GetBlockHashAsync(height, cancellationToken); + } + + Task IBlockChain.GetActionAsync( + TxId txId, int actionIndex, CancellationToken cancellationToken) + { + if (IsRunning is false || _blockChain is null) + { + throw new InvalidOperationException("BlockChain is not running."); + } + + return _blockChain.GetActionAsync(txId, actionIndex, cancellationToken); + } + + private void UpdateCurrent(INode? node) + { + if (_blockChain is not null) + { + _blockChain.Started -= BlockChain_Started; + _blockChain.Stopped -= BlockChain_Stopped; + _blockChain.BlockAppended -= BlockChain_BlockAppended; + if (_blockChain.IsRunning is false) + { + Tip = BlockInfo.Empty; + IsRunning = false; + _logger.LogDebug("BlockChain is stopped."); + Stopped?.Invoke(this, EventArgs.Empty); + } + } + + _blockChain = node?.GetKeyedService(INode.Key); + + if (_blockChain is not null) + { + if (_blockChain.IsRunning is true) + { + Tip = _blockChain.Tip; + IsRunning = true; + _logger.LogDebug("BlockChain is started."); + Started?.Invoke(this, EventArgs.Empty); + } + + _blockChain.Started += BlockChain_Started; + _blockChain.Stopped += BlockChain_Stopped; + _blockChain.BlockAppended += BlockChain_BlockAppended; + } + } + + private void Nodes_CurrentChanged(object? sender, EventArgs e) + => UpdateCurrent(_nodes.Current); + + private void BlockChain_BlockAppended(object? sender, BlockEventArgs e) + { + Tip = e.BlockInfo; + BlockAppended?.Invoke(sender, e); + } + + private void BlockChain_Started(object? sender, EventArgs e) + { + if (sender is IBlockChain blockChain && blockChain == _blockChain) + { + Tip = _blockChain.Tip; + IsRunning = true; + _logger.LogDebug("BlockChain is started."); + Started?.Invoke(this, EventArgs.Empty); + } + else + { + throw new UnreachableException("The sender is not an instance of IBlockChain."); + } + } + + private void BlockChain_Stopped(object? sender, EventArgs e) + { + Tip = BlockInfo.Empty; + IsRunning = false; + _logger.LogDebug("BlockChain is stopped."); + Stopped?.Invoke(this, EventArgs.Empty); + } +} diff --git a/src/console/LibplanetConsole.Console/Client.BlockChain.cs b/src/console/LibplanetConsole.Console/Client.BlockChain.cs index 99c50032..e2583363 100644 --- a/src/console/LibplanetConsole.Console/Client.BlockChain.cs +++ b/src/console/LibplanetConsole.Console/Client.BlockChain.cs @@ -11,6 +11,8 @@ internal sealed partial class Client public event EventHandler? BlockAppended; + public BlockInfo Tip => Info.Tip; + public async Task GetNextNonceAsync(Address address, CancellationToken cancellationToken) { if (_blockChainService is null) diff --git a/src/console/LibplanetConsole.Console/Client.cs b/src/console/LibplanetConsole.Console/Client.cs index 3ce7533b..45ccd699 100644 --- a/src/console/LibplanetConsole.Console/Client.cs +++ b/src/console/LibplanetConsole.Console/Client.cs @@ -25,6 +25,7 @@ internal sealed partial class Client : IClient, IBlockChain private bool _isDisposed; private ClientProcess? _process; private Task _processTask = Task.CompletedTask; + private IClientContent[]? _contents; public Client(IServiceProvider serviceProvider, ClientOptions clientOptions) { @@ -58,6 +59,12 @@ public Client(IServiceProvider serviceProvider, ClientOptions clientOptions) public ClientInfo Info => _clientInfo; + public IClientContent[] Contents + { + get => _contents ?? throw new InvalidOperationException("Contents is not initialized."); + set => _contents = value; + } + public object? GetService(Type serviceType) => _serviceProvider.GetService(serviceType); public override string ToString() => $"{Address.ToShortString()}: {EndPoint}"; @@ -97,7 +104,7 @@ public async Task AttachAsync(CancellationToken cancellationToken) try { await clientService.StartAsync(cancellationToken); - await blockChainService.StartAsync(cancellationToken); + await blockChainService.InitializeAsync(cancellationToken); } catch { @@ -172,6 +179,8 @@ public async Task StartAsync(INode node, CancellationToken cancellationToken) _clientInfo = response.ClientInfo; IsRunning = true; _logger.LogDebug("Client is started: {Address}", Address); + await Task.WhenAll(Contents.Select(item => item.StartAsync(cancellationToken))); + _logger.LogDebug("Client Contents are started: {Address}", Address); Started?.Invoke(this, EventArgs.Empty); } @@ -188,6 +197,9 @@ public async Task StopAsync(CancellationToken cancellationToken) throw new InvalidOperationException("Client is not attached."); } + await Task.WhenAll(Contents.Select(item => item.StopAsync(cancellationToken))); + _logger.LogDebug("Client Contents are stopped: {Address}", Address); + var request = new StopRequest(); var callOptions = new CallOptions(cancellationToken: cancellationToken); await _clientService.StopAsync(request, callOptions); @@ -276,7 +288,7 @@ private void ClientService_Stopped(object? sender, EventArgs e) private void BlockChainService_BlockAppended(object? sender, BlockEventArgs e) { - _clientInfo = _clientInfo with { TipHash = e.BlockInfo.Hash }; + _clientInfo = _clientInfo with { Tip = e.BlockInfo }; BlockAppended?.Invoke(this, e); } diff --git a/src/console/LibplanetConsole.Console/ClientContentBase.cs b/src/console/LibplanetConsole.Console/ClientContentBase.cs index 07bdd39d..3e8f7425 100644 --- a/src/console/LibplanetConsole.Console/ClientContentBase.cs +++ b/src/console/LibplanetConsole.Console/ClientContentBase.cs @@ -1,63 +1,38 @@ namespace LibplanetConsole.Console; -public abstract class ClientContentBase : IClientContent, IDisposable +public abstract class ClientContentBase(string name) : IClientContent, IDisposable { - private readonly string _name; - private bool _isDisposed; - - protected ClientContentBase(IClient client, string name) - { - _name = name; - Client = client; - Client.Attached += Client_Attached; - Client.Detached += Client_Detached; - Client.Started += Client_Started; - Client.Stopped += Client_Stopped; - } - - protected ClientContentBase(IClient client) - : this(client, string.Empty) - { - } - - public IClient Client { get; } + private readonly string _name = name; + private bool disposedValue; public string Name => _name != string.Empty ? _name : GetType().Name; void IDisposable.Dispose() { - if (_isDisposed is false) - { - Client.Attached -= Client_Attached; - Client.Detached -= Client_Detached; - Client.Started -= Client_Started; - Client.Stopped -= Client_Stopped; - _isDisposed = true; - GC.SuppressFinalize(this); - } + OnDispose(disposing: true); + GC.SuppressFinalize(this); } - protected virtual void OnClientAttached() - { - } + Task IClientContent.StartAsync(CancellationToken cancellationToken) + => OnStartAsync(cancellationToken); - protected virtual void OnClientDetached() - { - } - - protected virtual void OnClientStarted() - { - } + Task IClientContent.StopAsync(CancellationToken cancellationToken) + => OnStopAsync(cancellationToken); - protected virtual void OnClientStopped() - { - } + protected abstract Task OnStartAsync(CancellationToken cancellationToken); - private void Client_Attached(object? sender, EventArgs e) => OnClientAttached(); + protected abstract Task OnStopAsync(CancellationToken cancellationToken); - private void Client_Detached(object? sender, EventArgs e) => OnClientDetached(); - - private void Client_Started(object? sender, EventArgs e) => OnClientStarted(); + protected virtual void OnDispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // do nothing + } - private void Client_Stopped(object? sender, EventArgs e) => OnClientStopped(); + disposedValue = true; + } + } } diff --git a/src/console/LibplanetConsole.Console/ClientFactory.cs b/src/console/LibplanetConsole.Console/ClientFactory.cs index 79fe6c7e..df93c438 100644 --- a/src/console/LibplanetConsole.Console/ClientFactory.cs +++ b/src/console/LibplanetConsole.Console/ClientFactory.cs @@ -9,7 +9,7 @@ internal static class ClientFactory private static readonly ConcurrentDictionary _valueByKey = []; private static readonly ConcurrentDictionary _scopeByClient = []; - public static Client Create(IServiceProvider serviceProvider) + public static Client Create(IServiceProvider serviceProvider, object? key) { if (_valueByKey.Remove(serviceProvider, out var descriptor) is true) { @@ -42,8 +42,10 @@ public static Client CreateNew(IServiceProvider serviceProvider, ClientOptions c }, (k, v) => v); - var client = serviceScope.ServiceProvider.GetRequiredService(); - serviceScope.ServiceProvider.GetServices(); + var scopedServiceProvider = serviceScope.ServiceProvider; + var key = IClient.Key; + var client = scopedServiceProvider.GetRequiredKeyedService(key); + client.Contents = [.. scopedServiceProvider.GetKeyedServices(key)]; return client; } diff --git a/src/console/LibplanetConsole.Console/Commands/ClientCommand.cs b/src/console/LibplanetConsole.Console/Commands/ClientCommand.cs index 19685fba..e57f3796 100644 --- a/src/console/LibplanetConsole.Console/Commands/ClientCommand.cs +++ b/src/console/LibplanetConsole.Console/Commands/ClientCommand.cs @@ -1,5 +1,6 @@ using JSSoft.Commands; using JSSoft.Terminals; +using LibplanetConsole.Blockchain; using LibplanetConsole.Common; using LibplanetConsole.Common.Actions; using LibplanetConsole.Common.Extensions; diff --git a/src/console/LibplanetConsole.Console/Commands/NodeCommand.cs b/src/console/LibplanetConsole.Console/Commands/NodeCommand.cs index e676883d..c7b3ee1a 100644 --- a/src/console/LibplanetConsole.Console/Commands/NodeCommand.cs +++ b/src/console/LibplanetConsole.Console/Commands/NodeCommand.cs @@ -1,5 +1,6 @@ using JSSoft.Commands; using JSSoft.Terminals; +using LibplanetConsole.Blockchain; using LibplanetConsole.Common; using LibplanetConsole.Common.Actions; using LibplanetConsole.Common.Extensions; diff --git a/src/console/LibplanetConsole.Console/Commands/TxCommand.cs b/src/console/LibplanetConsole.Console/Commands/TxCommand.cs index 320fa5c5..1f3d7a3e 100644 --- a/src/console/LibplanetConsole.Console/Commands/TxCommand.cs +++ b/src/console/LibplanetConsole.Console/Commands/TxCommand.cs @@ -1,4 +1,5 @@ using JSSoft.Commands; +using LibplanetConsole.Blockchain; using LibplanetConsole.Common.Actions; using LibplanetConsole.Common.Extensions; using Microsoft.Extensions.DependencyInjection; diff --git a/src/console/LibplanetConsole.Console/IBlockChain.cs b/src/console/LibplanetConsole.Console/IBlockChain.cs deleted file mode 100644 index f6b6fe69..00000000 --- a/src/console/LibplanetConsole.Console/IBlockChain.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Security.Cryptography; -using LibplanetConsole.Blockchain; - -namespace LibplanetConsole.Console; - -public interface IBlockChain -{ - event EventHandler? BlockAppended; - - Task SendTransactionAsync(IAction[] actions, CancellationToken cancellationToken); - - Task GetNextNonceAsync(Address address, CancellationToken cancellationToken); - - Task GetTipHashAsync(CancellationToken cancellationToken); - - Task GetStateAsync( - BlockHash? blockHash, - Address accountAddress, - Address address, - CancellationToken cancellationToken); - - Task GetStateByStateRootHashAsync( - HashDigest stateRootHash, - Address accountAddress, - Address address, - CancellationToken cancellationToken); - - Task GetBlockHashAsync(long height, CancellationToken cancellationToken); - - Task GetActionAsync(TxId txId, int actionIndex, CancellationToken cancellationToken) - where T : IAction; -} diff --git a/src/console/LibplanetConsole.Console/IClient.cs b/src/console/LibplanetConsole.Console/IClient.cs index 0267522b..6d97ed36 100644 --- a/src/console/LibplanetConsole.Console/IClient.cs +++ b/src/console/LibplanetConsole.Console/IClient.cs @@ -5,6 +5,8 @@ namespace LibplanetConsole.Console; public interface IClient : IAddressable, IAsyncDisposable, IServiceProvider, ISigner { + const string Key = nameof(IClient); + event EventHandler? Attached; event EventHandler? Detached; diff --git a/src/console/LibplanetConsole.Console/IClientContent.cs b/src/console/LibplanetConsole.Console/IClientContent.cs index 6d42b653..e8aae15a 100644 --- a/src/console/LibplanetConsole.Console/IClientContent.cs +++ b/src/console/LibplanetConsole.Console/IClientContent.cs @@ -2,7 +2,9 @@ namespace LibplanetConsole.Console; public interface IClientContent { - IClient Client { get; } - string Name { get; } + + Task StartAsync(CancellationToken cancellationToken); + + Task StopAsync(CancellationToken cancellationToken); } diff --git a/src/console/LibplanetConsole.Console/INode.cs b/src/console/LibplanetConsole.Console/INode.cs index f99a95f8..9b84556b 100644 --- a/src/console/LibplanetConsole.Console/INode.cs +++ b/src/console/LibplanetConsole.Console/INode.cs @@ -1,10 +1,13 @@ using LibplanetConsole.Common; using LibplanetConsole.Node; +using Microsoft.Extensions.DependencyInjection; namespace LibplanetConsole.Console; -public interface INode : IAddressable, IAsyncDisposable, IServiceProvider, ISigner +public interface INode : IAddressable, IAsyncDisposable, IKeyedServiceProvider, ISigner { + const string Key = nameof(INode); + event EventHandler? Attached; event EventHandler? Detached; diff --git a/src/console/LibplanetConsole.Console/INodeContent.cs b/src/console/LibplanetConsole.Console/INodeContent.cs index 9550519e..96576087 100644 --- a/src/console/LibplanetConsole.Console/INodeContent.cs +++ b/src/console/LibplanetConsole.Console/INodeContent.cs @@ -2,7 +2,9 @@ namespace LibplanetConsole.Console; public interface INodeContent { - INode Node { get; } - string Name { get; } + + Task StartAsync(CancellationToken cancellationToken); + + Task StopAsync(CancellationToken cancellationToken); } diff --git a/src/console/LibplanetConsole.Console/Node.BlockChain.cs b/src/console/LibplanetConsole.Console/Node.BlockChain.cs index 91240665..74371991 100644 --- a/src/console/LibplanetConsole.Console/Node.BlockChain.cs +++ b/src/console/LibplanetConsole.Console/Node.BlockChain.cs @@ -11,6 +11,8 @@ internal sealed partial class Node public event EventHandler? BlockAppended; + public BlockInfo Tip => Info.Tip; + public async Task GetNextNonceAsync(Address address, CancellationToken cancellationToken) { if (_blockChainService is null) diff --git a/src/console/LibplanetConsole.Console/Node.cs b/src/console/LibplanetConsole.Console/Node.cs index cc2dd787..282d908d 100644 --- a/src/console/LibplanetConsole.Console/Node.cs +++ b/src/console/LibplanetConsole.Console/Node.cs @@ -29,6 +29,7 @@ internal sealed partial class Node : INode, IBlockChain private bool _isDisposed; private NodeProcess? _process; private Task _processTask = Task.CompletedTask; + private INodeContent[]? _contents; public Node(IServiceProvider serviceProvider, NodeOptions nodeOptions) { @@ -68,8 +69,34 @@ public EndPoint ConsensusEndPoint public NodeInfo Info => _nodeInfo; + public INodeContent[] Contents + { + get => _contents ?? throw new InvalidOperationException("Contents is not initialized."); + set => _contents = value; + } + public object? GetService(Type serviceType) => _serviceProvider.GetService(serviceType); + public object? GetKeyedService(Type serviceType, object? serviceKey) + { + if (_serviceProvider is IKeyedServiceProvider serviceProvider) + { + return serviceProvider.GetKeyedService(serviceType, serviceKey); + } + + throw new InvalidOperationException("Service provider does not support keyed service."); + } + + public object GetRequiredKeyedService(Type serviceType, object? serviceKey) + { + if (_serviceProvider is IKeyedServiceProvider serviceProvider) + { + return serviceProvider.GetRequiredKeyedService(serviceType, serviceKey); + } + + throw new InvalidOperationException("Service provider does not support keyed service."); + } + public override string ToString() => $"{Address.ToShortString()}: {EndPoint}"; public byte[] Sign(object obj) => _nodeOptions.PrivateKey.Sign(obj); @@ -93,6 +120,7 @@ public async Task GetInfoAsync(CancellationToken cancellationToken) public async Task AttachAsync(CancellationToken cancellationToken) { ObjectDisposedException.ThrowIf(_isDisposed, this); + if (_channel is not null) { throw new InvalidOperationException("Node is already attached."); @@ -106,8 +134,8 @@ public async Task AttachAsync(CancellationToken cancellationToken) blockChainService.BlockAppended += BlockChainService_BlockAppended; try { - await nodeService.StartAsync(cancellationToken); - await blockChainService.StartAsync(cancellationToken); + await nodeService.InitializeAsync(cancellationToken); + await blockChainService.InitializeAsync(cancellationToken); } catch { @@ -185,6 +213,8 @@ public async Task StartAsync(CancellationToken cancellationToken) _consensusEndPoint = EndPointUtility.Parse(_nodeInfo.ConsensusEndPoint); IsRunning = true; _logger.LogDebug("Node is started: {Address}", Address); + await Task.WhenAll(Contents.Select(item => item.StartAsync(cancellationToken))); + _logger.LogDebug("Node Contents are started: {Address}", Address); Started?.Invoke(this, EventArgs.Empty); } @@ -201,6 +231,9 @@ public async Task StopAsync(CancellationToken cancellationToken) throw new InvalidOperationException("Node is not attached."); } + await Task.WhenAll(Contents.Select(item => item.StopAsync(cancellationToken))); + _logger.LogDebug("Node Contents are stopped: {Address}", Address); + var request = new StopRequest(); var callOptions = new CallOptions(cancellationToken: cancellationToken); await _nodeService.StopAsync(request, callOptions); @@ -286,7 +319,7 @@ private void NodeService_Stopped(object? sender, EventArgs e) private void BlockChainService_BlockAppended(object? sender, BlockEventArgs e) { - _nodeInfo = _nodeInfo with { TipHash = e.BlockInfo.Hash }; + _nodeInfo = _nodeInfo with { Tip = e.BlockInfo }; BlockAppended?.Invoke(this, e); } diff --git a/src/console/LibplanetConsole.Console/NodeContentBase.cs b/src/console/LibplanetConsole.Console/NodeContentBase.cs index 07b02c75..fddd4bd0 100644 --- a/src/console/LibplanetConsole.Console/NodeContentBase.cs +++ b/src/console/LibplanetConsole.Console/NodeContentBase.cs @@ -1,63 +1,38 @@ namespace LibplanetConsole.Console; -public abstract class NodeContentBase : INodeContent, IDisposable +public abstract class NodeContentBase(string name) : INodeContent, IDisposable { - private readonly string _name; - private bool _isDisposed; - - protected NodeContentBase(INode node, string name) - { - _name = name; - Node = node; - Node.Attached += Node_Attached; - Node.Detached += Node_Detached; - Node.Started += Node_Started; - Node.Stopped += Node_Stopped; - } - - protected NodeContentBase(INode node) - : this(node, string.Empty) - { - } - - public INode Node { get; } + private readonly string _name = name; + private bool disposedValue; public string Name => _name != string.Empty ? _name : GetType().Name; void IDisposable.Dispose() { - if (_isDisposed is false) - { - Node.Attached -= Node_Attached; - Node.Detached -= Node_Detached; - Node.Started -= Node_Started; - Node.Stopped -= Node_Stopped; - _isDisposed = true; - GC.SuppressFinalize(this); - } + OnDispose(disposing: true); + GC.SuppressFinalize(this); } - protected virtual void OnNodeAttached() - { - } + Task INodeContent.StartAsync(CancellationToken cancellationToken) + => OnStartAsync(cancellationToken); - protected virtual void OnNodeDetached() - { - } - - protected virtual void OnNodeStarted() - { - } + Task INodeContent.StopAsync(CancellationToken cancellationToken) + => OnStopAsync(cancellationToken); - protected virtual void OnNodeStopped() - { - } + protected abstract Task OnStartAsync(CancellationToken cancellationToken); - private void Node_Attached(object? sender, EventArgs e) => OnNodeAttached(); + protected abstract Task OnStopAsync(CancellationToken cancellationToken); - private void Node_Detached(object? sender, EventArgs e) => OnNodeDetached(); - - private void Node_Started(object? sender, EventArgs e) => OnNodeStarted(); + protected virtual void OnDispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // do nothing + } - private void Node_Stopped(object? sender, EventArgs e) => OnNodeStopped(); + disposedValue = true; + } + } } diff --git a/src/console/LibplanetConsole.Console/NodeFactory.cs b/src/console/LibplanetConsole.Console/NodeFactory.cs index 8fbe251e..f4202331 100644 --- a/src/console/LibplanetConsole.Console/NodeFactory.cs +++ b/src/console/LibplanetConsole.Console/NodeFactory.cs @@ -9,7 +9,7 @@ internal static class NodeFactory private static readonly ConcurrentDictionary _valueByKey = []; private static readonly ConcurrentDictionary _scopeByNode = []; - public static Node Create(IServiceProvider serviceProvider) + public static Node Create(IServiceProvider serviceProvider, object? key) { if (_valueByKey.Remove(serviceProvider, out var descriptor) is true) { @@ -42,8 +42,10 @@ public static Node CreateNew(IServiceProvider serviceProvider, NodeOptions nodeO }, (k, v) => v); - var node = serviceScope.ServiceProvider.GetRequiredService(); - serviceScope.ServiceProvider.GetServices(); + var scopedServiceProvider = serviceScope.ServiceProvider; + var key = INode.Key; + var node = scopedServiceProvider.GetRequiredKeyedService(key); + node.Contents = [.. scopedServiceProvider.GetKeyedServices(key)]; return node; } diff --git a/src/console/LibplanetConsole.Console/ServiceCollectionExtensions.cs b/src/console/LibplanetConsole.Console/ServiceCollectionExtensions.cs index b3466410..ce9c6fc0 100644 --- a/src/console/LibplanetConsole.Console/ServiceCollectionExtensions.cs +++ b/src/console/LibplanetConsole.Console/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ using JSSoft.Commands; +using LibplanetConsole.Blockchain; using LibplanetConsole.Common; using LibplanetConsole.Console.Commands; using LibplanetConsole.Seed; @@ -8,35 +9,46 @@ namespace LibplanetConsole.Console; public static class ServiceCollectionExtensions { - public static IServiceCollection AddConsole( - this IServiceCollection @this, ApplicationOptions options) - { - @this.AddSingleton(options); - @this.AddSingleton() - .AddSingleton(s => s.GetRequiredService()); - @this.AddSingleton() - .AddSingleton(s => s.GetRequiredService()); - @this.AddSingleton() - .AddSingleton(s => s.GetRequiredService()); - @this.AddHostedService(); + public static IServiceCollection AddConsole( + this IServiceCollection @this, ApplicationOptions options) + { + var synchronizationContext = SynchronizationContext.Current ?? new(); + SynchronizationContext.SetSynchronizationContext(synchronizationContext); + @this.AddSingleton(synchronizationContext); + @this.AddSingleton(options); + @this.AddSingleton() + .AddSingleton(s => s.GetRequiredService()); + @this.AddSingleton() + .AddSingleton(s => s.GetRequiredService()); + @this.AddSingleton() + .AddSingleton(s => s.GetRequiredService()); + @this.AddSingleton() + .AddSingleton(s => s.GetRequiredService()); - @this.AddScoped(NodeFactory.Create) - .AddScoped(s => s.GetRequiredService()) - .AddScoped(s => s.GetRequiredService()); - @this.AddScoped(ClientFactory.Create) - .AddScoped(s => s.GetRequiredService()); + @this.AddHostedService(); - @this.AddSingleton(); - @this.AddSingleton(); + @this.AddKeyedScoped(INode.Key, NodeFactory.Create) + .AddKeyedScoped( + INode.Key, (s, k) => s.GetRequiredKeyedService(k)) + .AddKeyedScoped( + INode.Key, (s, k) => s.GetRequiredKeyedService(k)); + @this.AddKeyedScoped(IClient.Key, ClientFactory.Create) + .AddKeyedScoped( + IClient.Key, (s, k) => s.GetRequiredKeyedService(k)) + .AddKeyedScoped( + IClient.Key, (s, k) => s.GetRequiredKeyedService(k)); - @this.AddSingleton() - .AddSingleton(s => s.GetRequiredService()); - @this.AddSingleton(); - @this.AddSingleton(); - @this.AddSingleton(); - @this.AddSingleton() - .AddSingleton(s => s.GetRequiredService()); - @this.AddSingleton(); - return @this; - } + @this.AddSingleton(); + @this.AddSingleton(); + + @this.AddSingleton() + .AddSingleton(s => s.GetRequiredService()); + @this.AddSingleton(); + @this.AddSingleton(); + @this.AddSingleton(); + @this.AddSingleton() + .AddSingleton(s => s.GetRequiredService()); + @this.AddSingleton(); + return @this; + } } diff --git a/src/node/LibplanetConsole.Node.Evidence/Services/EvidenceServiceGrpcV1.cs b/src/node/LibplanetConsole.Node.Evidence/Grpc/EvidenceServiceGrpcV1.cs similarity index 86% rename from src/node/LibplanetConsole.Node.Evidence/Services/EvidenceServiceGrpcV1.cs rename to src/node/LibplanetConsole.Node.Evidence/Grpc/EvidenceServiceGrpcV1.cs index b6c2df2c..9c11f28a 100644 --- a/src/node/LibplanetConsole.Node.Evidence/Services/EvidenceServiceGrpcV1.cs +++ b/src/node/LibplanetConsole.Node.Evidence/Grpc/EvidenceServiceGrpcV1.cs @@ -1,7 +1,7 @@ using Grpc.Core; using LibplanetConsole.Evidence.Grpc; -namespace LibplanetConsole.Node.Evidence.Services; +namespace LibplanetConsole.Node.Evidence.Grpc; internal sealed class EvidenceServiceGrpcV1(Evidence evidence) : EvidenceGrpcService.EvidenceGrpcServiceBase @@ -12,7 +12,7 @@ public override async Task AddEvidence( var evidenceInfo = await evidence.AddEvidenceAsync(context.CancellationToken); return new AddEvidenceResponse { - EvidenceInfo = evidenceInfo, + EvidenceInformation = evidenceInfo, }; } @@ -24,7 +24,7 @@ public override async Task GetEvidence( var response = new GetEvidenceResponse(); for (var i = 0; i < evidenceInfos.Length; i++) { - response.EvidenceInfos.Add(evidenceInfos[i]); + response.EvidenceInformations.Add(evidenceInfos[i]); } return response; diff --git a/src/node/LibplanetConsole.Node.Evidence/NodeEndpointRouteBuilderExtensions.cs b/src/node/LibplanetConsole.Node.Evidence/NodeEndpointRouteBuilderExtensions.cs new file mode 100644 index 00000000..1380097e --- /dev/null +++ b/src/node/LibplanetConsole.Node.Evidence/NodeEndpointRouteBuilderExtensions.cs @@ -0,0 +1,15 @@ +using LibplanetConsole.Node.Evidence.Grpc; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing; + +namespace LibplanetConsole.Node.Evidence; + +public static class NodeEndpointRouteBuilderExtensions +{ + public static IEndpointRouteBuilder UseEvidence(this IEndpointRouteBuilder @this) + { + @this.MapGrpcService(); + + return @this; + } +} diff --git a/src/node/LibplanetConsole.Node.Evidence/ServiceCollectionExtensions.cs b/src/node/LibplanetConsole.Node.Evidence/ServiceCollectionExtensions.cs index 8eb9bf5e..dc13fb40 100644 --- a/src/node/LibplanetConsole.Node.Evidence/ServiceCollectionExtensions.cs +++ b/src/node/LibplanetConsole.Node.Evidence/ServiceCollectionExtensions.cs @@ -10,7 +10,6 @@ public static IServiceCollection AddEvidence(this IServiceCollection @this) { @this.AddSingleton() .AddSingleton(s => s.GetRequiredService()); - // @this.AddSingleton(); @this.AddSingleton(); return @this; } diff --git a/src/node/LibplanetConsole.Node.Executable/Application.cs b/src/node/LibplanetConsole.Node.Executable/Application.cs index d2dfc96a..bdb10619 100644 --- a/src/node/LibplanetConsole.Node.Executable/Application.cs +++ b/src/node/LibplanetConsole.Node.Executable/Application.cs @@ -1,6 +1,7 @@ using JSSoft.Commands; using LibplanetConsole.Common; using LibplanetConsole.Logging; +using LibplanetConsole.Node.Evidence; using LibplanetConsole.Node.Executable.Commands; using LibplanetConsole.Node.Executable.Tracers; using LibplanetConsole.Node.Explorer; @@ -34,7 +35,10 @@ public Application(ApplicationOptions options, object[] instances) options.ListenLocalhost(port, o => o.Protocols = HttpProtocols.Http2); }); - services.AddLogging(options.LogPath, "node.log", _filters); + if (options.LogPath != string.Empty) + { + services.AddLogging(options.LogPath, "node.log", _filters); + } services.AddSingleton(); services.AddSingleton(); @@ -46,6 +50,7 @@ public Application(ApplicationOptions options, object[] instances) services.AddNode(options); services.AddExplorer(_builder.Configuration); + services.AddEvidence(); services.AddGrpc(); services.AddGrpcReflection(); @@ -61,6 +66,7 @@ public async Task RunAsync(CancellationToken cancellationToken) app.UseNode(); app.UseExplorer(); + app.UseEvidence(); app.MapGet("/", () => "Libplanet-Node"); app.UseAuthentication(); app.UseAuthorization(); diff --git a/src/node/LibplanetConsole.Node.Executable/EntryCommands/InitializeCommand.cs b/src/node/LibplanetConsole.Node.Executable/EntryCommands/InitializeCommand.cs index 11590177..f1c1df52 100644 --- a/src/node/LibplanetConsole.Node.Executable/EntryCommands/InitializeCommand.cs +++ b/src/node/LibplanetConsole.Node.Executable/EntryCommands/InitializeCommand.cs @@ -41,8 +41,7 @@ public InitializeCommand() public string StorePath { get; set; } = string.Empty; [CommandProperty] - [CommandSummary("The file path to store the application logs." + - "If omitted, the 'app.log' file is used.")] + [CommandSummary("Indicates the file path to save logs.")] [Path(Type = PathType.Directory, AllowEmpty = true)] public string LogPath { get; set; } = string.Empty; diff --git a/src/node/LibplanetConsole.Node.Executable/SystemTerminal.cs b/src/node/LibplanetConsole.Node.Executable/SystemTerminal.cs index de00a093..ea3f469f 100644 --- a/src/node/LibplanetConsole.Node.Executable/SystemTerminal.cs +++ b/src/node/LibplanetConsole.Node.Executable/SystemTerminal.cs @@ -1,22 +1,67 @@ +using System.Text; using JSSoft.Commands.Extensions; using JSSoft.Terminals; +using LibplanetConsole.Blockchain; +using LibplanetConsole.Common.Extensions; namespace LibplanetConsole.Node.Executable; internal sealed class SystemTerminal : SystemTerminalBase { + private const string PromptText = "libplanet-node $ "; + private readonly SynchronizationContext _synchronizationContext; private readonly CommandContext _commandContext; + private readonly IBlockChain _blockChain; + private BlockInfo _tip; public SystemTerminal( - IHostApplicationLifetime applicationLifetime, CommandContext commandContext) + IHostApplicationLifetime applicationLifetime, + CommandContext commandContext, + IBlockChain blockChain, + SynchronizationContext synchronizationContext) { + _synchronizationContext = synchronizationContext; _commandContext = commandContext; _commandContext.Owner = applicationLifetime; - Prompt = "libplanet-node $ "; + _blockChain = blockChain; + _blockChain.BlockAppended += BlockChain_BlockAppended; + _blockChain.Started += BlockChain_Started; + _blockChain.Stopped += BlockChain_Stopped; + UpdatePrompt(_blockChain.Tip); applicationLifetime.ApplicationStopping.Register(() => Prompt = "\u001b0"); } - protected override string FormatPrompt(string prompt) => prompt; + protected override void OnDispose() + { + _blockChain.Started -= BlockChain_Started; + _blockChain.Stopped -= BlockChain_Stopped; + _blockChain.BlockAppended -= BlockChain_BlockAppended; + base.OnDispose(); + } + + protected override string FormatPrompt(string prompt) + { + var tip = _tip; + if (_tip.Height == -1) + { + return prompt; + } + else + { + var tsb = new TerminalStringBuilder(); + tsb.AppendEnd(); + tsb.Append($"#{tip.Height} "); + tsb.Foreground = TerminalColorType.BrightGreen; + tsb.Append($"{tip.Hash.ToShortString()} "); + tsb.ResetOptions(); + tsb.Append($"by "); + tsb.Foreground = TerminalColorType.BrightGreen; + tsb.Append($"{tip.Miner.ToShortString()}"); + tsb.ResetOptions(); + tsb.AppendEnd(); + return $"[{tsb}] {PromptText}"; + } + } protected override string[] GetCompletion(string[] items, string find) => _commandContext.GetCompletion(items, find); @@ -29,4 +74,32 @@ protected override void OnInitialize(TextWriter @out, TextWriter error) _commandContext.Out = @out; _commandContext.Error = error; } + + private void BlockChain_BlockAppended(object? sender, BlockEventArgs e) + => _synchronizationContext.Post(_ => UpdatePrompt(e.BlockInfo), null); + + private void BlockChain_Started(object? sender, EventArgs e) + => _synchronizationContext.Post(_ => UpdatePrompt(_blockChain.Tip), null); + + private void BlockChain_Stopped(object? sender, EventArgs e) + => _synchronizationContext.Post(_ => UpdatePrompt(_blockChain.Tip), null); + + private void UpdatePrompt(BlockInfo tip) + { + if (tip.Height == -1) + { + Prompt = PromptText; + } + else + { + var sb = new StringBuilder(); + sb.Append($"#{tip.Height} "); + sb.Append($"{tip.Hash.ToShortString()} "); + sb.Append($"by "); + sb.Append($"{tip.Miner.ToShortString()}"); + Prompt = $"[{sb}] {PromptText}"; + } + + _tip = tip; + } } diff --git a/src/node/LibplanetConsole.Node.Executable/Tracers/BlockChainEventTracer.cs b/src/node/LibplanetConsole.Node.Executable/Tracers/BlockChainEventTracer.cs index a07e24ea..3d0c23d1 100644 --- a/src/node/LibplanetConsole.Node.Executable/Tracers/BlockChainEventTracer.cs +++ b/src/node/LibplanetConsole.Node.Executable/Tracers/BlockChainEventTracer.cs @@ -33,9 +33,10 @@ private void Node_BlockAppended(object? sender, BlockEventArgs e) var blockInfo = e.BlockInfo; var hash = blockInfo.Hash; var miner = blockInfo.Miner; - var message = $"Block #{blockInfo.Height} '{hash.ToShortString()}' " + - $"Appended by '{miner.ToShortString()}'"; - Console.Out.WriteColoredLine(message, TerminalColorType.BrightGreen); - _logger.LogInformation(message); + _logger.LogInformation( + "Block #{TipHeight} '{TipHash}' Appended by '{TipMiner}'", + blockInfo.Height, + hash.ToShortString(), + miner.ToShortString()); } } diff --git a/src/node/LibplanetConsole.Node/Commands/TxCommand.cs b/src/node/LibplanetConsole.Node/Commands/TxCommand.cs index 40bceff1..fe01c09d 100644 --- a/src/node/LibplanetConsole.Node/Commands/TxCommand.cs +++ b/src/node/LibplanetConsole.Node/Commands/TxCommand.cs @@ -1,4 +1,5 @@ using JSSoft.Commands; +using LibplanetConsole.Blockchain; using LibplanetConsole.Common.Actions; using LibplanetConsole.Common.Extensions; diff --git a/src/node/LibplanetConsole.Node/Grpc/BlockChainGrpcServiceV1.cs b/src/node/LibplanetConsole.Node/Grpc/BlockChainGrpcServiceV1.cs index 7a5ec726..bafb213f 100644 --- a/src/node/LibplanetConsole.Node/Grpc/BlockChainGrpcServiceV1.cs +++ b/src/node/LibplanetConsole.Node/Grpc/BlockChainGrpcServiceV1.cs @@ -48,7 +48,7 @@ public async override Task GetNextNonce( public override async Task GetTipHash( GetTipHashRequest request, ServerCallContext context) { - var blockHash = await _blockChain.GetTipHashAsync(context.CancellationToken); + var blockHash = await Task.FromResult(_blockChain.Tip.Hash); return new GetTipHashResponse { BlockHash = blockHash.ToString() }; } diff --git a/src/node/LibplanetConsole.Node/Grpc/NodeGrpcServiceV1.cs b/src/node/LibplanetConsole.Node/Grpc/NodeGrpcServiceV1.cs index 59172fe0..331fa3e9 100644 --- a/src/node/LibplanetConsole.Node/Grpc/NodeGrpcServiceV1.cs +++ b/src/node/LibplanetConsole.Node/Grpc/NodeGrpcServiceV1.cs @@ -6,11 +6,12 @@ namespace LibplanetConsole.Node.Grpc; -internal sealed class NodeGrpcServiceV1 : NodeGrpcService.NodeGrpcServiceBase +internal sealed class NodeGrpcServiceV1 : NodeGrpcService.NodeGrpcServiceBase, IDisposable { private readonly IHostApplicationLifetime _applicationLifetime; private readonly Node _node; private readonly ILogger _logger; + private bool _isDisposed; public NodeGrpcServiceV1( IHostApplicationLifetime applicationLifetime, @@ -75,4 +76,13 @@ public override async Task GetStoppedStream( handler => _node.Stopped -= handler); await streamer.RunAsync(_applicationLifetime, context.CancellationToken); } + + public void Dispose() + { + if (_isDisposed is false) + { + _logger.LogDebug("{GrpcServiceType} is disposed.", nameof(NodeGrpcServiceV1)); + _isDisposed = true; + } + } } diff --git a/src/node/LibplanetConsole.Node/IBlockChain.cs b/src/node/LibplanetConsole.Node/IBlockChain.cs deleted file mode 100644 index e410ad4d..00000000 --- a/src/node/LibplanetConsole.Node/IBlockChain.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Security.Cryptography; -using LibplanetConsole.Blockchain; - -namespace LibplanetConsole.Node; - -public interface IBlockChain -{ - event EventHandler? BlockAppended; - - Task SendTransactionAsync(IAction[] actions, CancellationToken cancellationToken); - - Task GetNextNonceAsync(Address address, CancellationToken cancellationToken); - - Task GetTipHashAsync(CancellationToken cancellationToken); - - Task GetStateAsync( - BlockHash? blockHash, - Address accountAddress, - Address address, - CancellationToken cancellationToken); - - Task GetStateByStateRootHashAsync( - HashDigest stateRootHash, - Address accountAddress, - Address address, - CancellationToken cancellationToken); - - Task GetBlockHashAsync(long height, CancellationToken cancellationToken); - - Task GetActionAsync(TxId txId, int actionIndex, CancellationToken cancellationToken) - where T : IAction; -} diff --git a/src/node/LibplanetConsole.Node/LibplanetConsole.Node.csproj b/src/node/LibplanetConsole.Node/LibplanetConsole.Node.csproj index 2f999b5f..60cdec0a 100644 --- a/src/node/LibplanetConsole.Node/LibplanetConsole.Node.csproj +++ b/src/node/LibplanetConsole.Node/LibplanetConsole.Node.csproj @@ -22,11 +22,11 @@ - - - - - + + + + + diff --git a/src/node/LibplanetConsole.Node/Node.BlockChain.cs b/src/node/LibplanetConsole.Node/Node.BlockChain.cs index 890e0c13..45519bdd 100644 --- a/src/node/LibplanetConsole.Node/Node.BlockChain.cs +++ b/src/node/LibplanetConsole.Node/Node.BlockChain.cs @@ -1,5 +1,6 @@ using System.Security.Cryptography; using System.Text; +using LibplanetConsole.Blockchain; using LibplanetConsole.Common; using LibplanetConsole.Common.Exceptions; using Microsoft.Extensions.Logging; @@ -10,6 +11,10 @@ internal sealed partial class Node : IBlockChain { private static readonly Codec _codec = new(); + public event EventHandler? BlockAppended; + + public BlockInfo Tip => Info.Tip; + public async Task SendTransactionAsync( IAction[] actions, CancellationToken cancellationToken) { diff --git a/src/node/LibplanetConsole.Node/Node.cs b/src/node/LibplanetConsole.Node/Node.cs index 8b954d7a..f49cbbee 100644 --- a/src/node/LibplanetConsole.Node/Node.cs +++ b/src/node/LibplanetConsole.Node/Node.cs @@ -54,8 +54,6 @@ public Node(IServiceProvider serviceProvider, ApplicationOptions options) _logger.LogDebug("Node is created: {Address}", Address); } - public event EventHandler? BlockAppended; - public event EventHandler? Started; public event EventHandler? Stopped; @@ -280,10 +278,8 @@ void Action(object? state) } } - var blockChain = _swarm!.BlockChain; - var blockInfo = new BlockInfo(blockChain, blockChain.Tip); UpdateNodeInfo(); - BlockAppended?.Invoke(this, new(blockInfo)); + BlockAppended?.Invoke(this, new(Info.Tip)); } } @@ -341,7 +337,7 @@ private void UpdateNodeInfo() SwarmEndPoint = EndPointUtility.ToString(SwarmEndPoint), ConsensusEndPoint = EndPointUtility.ToString(ConsensusEndPoint), GenesisHash = BlockChain.Genesis.Hash, - TipHash = BlockChain.Tip.Hash, + Tip = new BlockInfo(BlockChain.Tip), IsRunning = IsRunning, }; } diff --git a/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs b/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs index bfc65575..cc2d16e2 100644 --- a/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs +++ b/src/node/LibplanetConsole.Node/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ using JSSoft.Commands; +using LibplanetConsole.Blockchain; using LibplanetConsole.Common; using LibplanetConsole.Node.Commands; using LibplanetConsole.Seed; diff --git a/src/shared/LibplanetConsole.Blockchain/BlockInfo.Node.cs b/src/shared/LibplanetConsole.Blockchain/BlockInfo.Node.cs index 3da91018..50c60fbb 100644 --- a/src/shared/LibplanetConsole.Blockchain/BlockInfo.Node.cs +++ b/src/shared/LibplanetConsole.Blockchain/BlockInfo.Node.cs @@ -8,7 +8,7 @@ namespace LibplanetConsole.Blockchain; public readonly partial record struct BlockInfo { - public BlockInfo(BlockChain blockChain, Block block) + public BlockInfo(Block block) { Height = block.Index; Hash = block.Hash; diff --git a/src/shared/LibplanetConsole.Blockchain/BlockInfo.cs b/src/shared/LibplanetConsole.Blockchain/BlockInfo.cs index 52af8475..1292cb07 100644 --- a/src/shared/LibplanetConsole.Blockchain/BlockInfo.cs +++ b/src/shared/LibplanetConsole.Blockchain/BlockInfo.cs @@ -14,6 +14,11 @@ public BlockInfo() public Address Miner { get; init; } + public static BlockInfo Empty { get; } = new BlockInfo + { + Height = -1, + }; + public static implicit operator BlockInfo(BlockInformation blockInfo) { return new BlockInfo diff --git a/src/shared/LibplanetConsole.Blockchain/Grpc/BlockChainService.cs b/src/shared/LibplanetConsole.Blockchain/Grpc/BlockChainService.cs index 46124843..84f40696 100644 --- a/src/shared/LibplanetConsole.Blockchain/Grpc/BlockChainService.cs +++ b/src/shared/LibplanetConsole.Blockchain/Grpc/BlockChainService.cs @@ -25,7 +25,7 @@ public void Dispose() } } - public async Task StartAsync(CancellationToken cancellationToken) + public async Task InitializeAsync(CancellationToken cancellationToken) { if (_blockAppendedReceiver is not null) { @@ -38,7 +38,7 @@ public async Task StartAsync(CancellationToken cancellationToken) await _blockAppendedReceiver.StartAsync(cancellationToken); } - public async Task StopAsync(CancellationToken cancellationToken) + public async Task ReleaseAsync(CancellationToken cancellationToken) { if (_blockAppendedReceiver is null) { diff --git a/src/client/LibplanetConsole.Client/IBlockChain.cs b/src/shared/LibplanetConsole.Blockchain/IBlockChain.cs similarity index 84% rename from src/client/LibplanetConsole.Client/IBlockChain.cs rename to src/shared/LibplanetConsole.Blockchain/IBlockChain.cs index b23a282e..71e6c652 100644 --- a/src/client/LibplanetConsole.Client/IBlockChain.cs +++ b/src/shared/LibplanetConsole.Blockchain/IBlockChain.cs @@ -1,18 +1,23 @@ using System.Security.Cryptography; -using LibplanetConsole.Blockchain; -namespace LibplanetConsole.Client; +namespace LibplanetConsole.Blockchain; public interface IBlockChain { event EventHandler? BlockAppended; + event EventHandler? Started; + + event EventHandler? Stopped; + + bool IsRunning { get; } + + BlockInfo Tip { get; } + Task SendTransactionAsync(IAction[] actions, CancellationToken cancellationToken); Task GetNextNonceAsync(Address address, CancellationToken cancellationToken); - Task GetTipHashAsync(CancellationToken cancellationToken); - Task GetStateAsync( BlockHash? blockHash, Address accountAddress, diff --git a/src/shared/LibplanetConsole.Client/ClientInfo.cs b/src/shared/LibplanetConsole.Client/ClientInfo.cs index 9a3a8ca3..3ecac980 100644 --- a/src/shared/LibplanetConsole.Client/ClientInfo.cs +++ b/src/shared/LibplanetConsole.Client/ClientInfo.cs @@ -1,3 +1,4 @@ +using LibplanetConsole.Blockchain; using LibplanetConsole.Client.Grpc; namespace LibplanetConsole.Client; @@ -10,12 +11,13 @@ public readonly record struct ClientInfo public BlockHash GenesisHash { get; init; } - public BlockHash TipHash { get; init; } + public BlockInfo Tip { get; init; } public bool IsRunning { get; init; } public static ClientInfo Empty { get; } = new ClientInfo { + Tip = BlockInfo.Empty, }; public static implicit operator ClientInfo(ClientInformation clientInfo) @@ -25,7 +27,12 @@ public static implicit operator ClientInfo(ClientInformation clientInfo) Address = new Address(clientInfo.Address), NodeAddress = new Address(clientInfo.NodeAddress), GenesisHash = BlockHash.FromString(clientInfo.GenesisHash), - TipHash = BlockHash.FromString(clientInfo.TipHash), + Tip = new BlockInfo + { + Hash = BlockHash.FromString(clientInfo.TipHash), + Height = clientInfo.TipHeight, + Miner = new Address(clientInfo.TipMiner), + }, IsRunning = clientInfo.IsRunning, }; } @@ -37,7 +44,9 @@ public static implicit operator ClientInformation(ClientInfo clientInfo) Address = clientInfo.Address.ToHex(), NodeAddress = clientInfo.NodeAddress.ToHex(), GenesisHash = clientInfo.GenesisHash.ToString(), - TipHash = clientInfo.TipHash.ToString(), + TipHash = clientInfo.Tip.Hash.ToString(), + TipHeight = clientInfo.Tip.Height, + TipMiner = clientInfo.Tip.Miner.ToHex(), IsRunning = clientInfo.IsRunning, }; } diff --git a/src/shared/LibplanetConsole.Client/Protos/ClientGrpcService.proto b/src/shared/LibplanetConsole.Client/Protos/ClientGrpcService.proto index 2adadc1b..c0e6378b 100644 --- a/src/shared/LibplanetConsole.Client/Protos/ClientGrpcService.proto +++ b/src/shared/LibplanetConsole.Client/Protos/ClientGrpcService.proto @@ -19,7 +19,9 @@ message ClientInformation { string node_address = 2; string genesis_hash = 3; string tip_hash = 4; - bool is_running = 5; + int64 tip_height = 5; + string tip_miner = 6; + bool is_running = 7; } message PingRequest { diff --git a/src/shared/LibplanetConsole.Evidence/EvidenceInfo.cs b/src/shared/LibplanetConsole.Evidence/EvidenceInfo.cs index fbbfbf06..b815ffc8 100644 --- a/src/shared/LibplanetConsole.Evidence/EvidenceInfo.cs +++ b/src/shared/LibplanetConsole.Evidence/EvidenceInfo.cs @@ -1,5 +1,5 @@ using Libplanet.Types.Evidence; -using GrpcEvidenceInfo = LibplanetConsole.Evidence.Grpc.EvidenceInfo; +using LibplanetConsole.Evidence.Grpc; namespace LibplanetConsole.Evidence; @@ -27,7 +27,7 @@ public static explicit operator EvidenceInfo(EvidenceBase evidence) }; } - public static implicit operator EvidenceInfo(GrpcEvidenceInfo evidenceInfo) + public static implicit operator EvidenceInfo(EvidenceInformation evidenceInfo) { return new EvidenceInfo { @@ -39,9 +39,9 @@ public static implicit operator EvidenceInfo(GrpcEvidenceInfo evidenceInfo) }; } - public static implicit operator GrpcEvidenceInfo(EvidenceInfo evidenceInfo) + public static implicit operator EvidenceInformation(EvidenceInfo evidenceInfo) { - return new GrpcEvidenceInfo + return new EvidenceInformation { Type = evidenceInfo.Type, Id = evidenceInfo.Id, diff --git a/src/shared/LibplanetConsole.Evidence/Protos/EvidenceGrpcService.proto b/src/shared/LibplanetConsole.Evidence/Protos/EvidenceGrpcService.proto index bd62d5b1..411fe014 100644 --- a/src/shared/LibplanetConsole.Evidence/Protos/EvidenceGrpcService.proto +++ b/src/shared/LibplanetConsole.Evidence/Protos/EvidenceGrpcService.proto @@ -12,7 +12,7 @@ service EvidenceGrpcService { rpc Violate(ViolateRequest) returns (ViolateResponse); } -message EvidenceInfo { +message EvidenceInformation { string type = 1; string id = 2; string targetAddress = 3; @@ -27,7 +27,7 @@ message AddEvidenceRequest { } message AddEvidenceResponse { - EvidenceInfo evidenceInfo = 1; + EvidenceInformation EvidenceInformation = 1; } message GetEvidenceRequest { @@ -35,7 +35,7 @@ message GetEvidenceRequest { } message GetEvidenceResponse { - repeated EvidenceInfo evidenceInfos = 1; + repeated EvidenceInformation EvidenceInformations = 1; } message ViolateRequest { diff --git a/src/shared/LibplanetConsole.Grpc/ConnectionMonitor.cs b/src/shared/LibplanetConsole.Grpc/ConnectionMonitor.cs index f45c90a5..3b028eb8 100644 --- a/src/shared/LibplanetConsole.Grpc/ConnectionMonitor.cs +++ b/src/shared/LibplanetConsole.Grpc/ConnectionMonitor.cs @@ -24,7 +24,7 @@ protected override async Task OnRunAsync(CancellationToken cancellationToken) { await action(client, cancellationToken); } - catch (RpcException e) + catch (RpcException) { Disconnected?.Invoke(this, EventArgs.Empty); break; diff --git a/src/shared/LibplanetConsole.Grpc/EventStreamer.cs b/src/shared/LibplanetConsole.Grpc/EventStreamer.cs index 81ba1926..e49124e4 100644 --- a/src/shared/LibplanetConsole.Grpc/EventStreamer.cs +++ b/src/shared/LibplanetConsole.Grpc/EventStreamer.cs @@ -11,10 +11,10 @@ internal sealed class EventStreamer( { protected async override Task OnRun(CancellationToken cancellationToken) { - void Handler(object? s, TEventArgs args) + async void Handler(object? s, TEventArgs args) { var value = selector(args); - WriteValue(value); + await WriteValueAsync(value); } attach(Handler); @@ -45,10 +45,10 @@ public EventStreamer( protected async override Task OnRun(CancellationToken cancellationToken) { - void Handler(object? s, EventArgs args) + async void Handler(object? s, EventArgs args) { var value = selector(); - WriteValue(value); + await WriteValueAsync(value); } attach(Handler); diff --git a/src/shared/LibplanetConsole.Grpc/Streamer.cs b/src/shared/LibplanetConsole.Grpc/Streamer.cs index 319f7bd8..1329485f 100644 --- a/src/shared/LibplanetConsole.Grpc/Streamer.cs +++ b/src/shared/LibplanetConsole.Grpc/Streamer.cs @@ -45,7 +45,7 @@ protected virtual async Task OnRun(CancellationToken cancellationToken) } } - protected async void WriteValue(T value) + protected async Task WriteValueAsync(T value) { if (_cancellationTokenSource is null) { diff --git a/src/shared/LibplanetConsole.Node/Grpc/NodeService.cs b/src/shared/LibplanetConsole.Node/Grpc/NodeService.cs index 858c00b5..649a0954 100644 --- a/src/shared/LibplanetConsole.Node/Grpc/NodeService.cs +++ b/src/shared/LibplanetConsole.Node/Grpc/NodeService.cs @@ -20,6 +20,8 @@ internal sealed class NodeService(GrpcChannel channel) public event EventHandler? Stopped; + public NodeInfo Info { get; private set; } + public void Dispose() { if (_isDisposed is false) @@ -34,7 +36,7 @@ public void Dispose() } } - public async Task StartAsync(CancellationToken cancellationToken) + public async Task InitializeAsync(CancellationToken cancellationToken) { if (_connection is not null) { @@ -53,9 +55,10 @@ public async Task StartAsync(CancellationToken cancellationToken) await Task.WhenAll( _startedReceiver.StartAsync(cancellationToken), _stoppedReceiver.StartAsync(cancellationToken)); + Info = (await GetInfoAsync(new(), cancellationToken: cancellationToken)).NodeInfo; } - public async Task StopAsync(CancellationToken cancellationToken) + public async Task ReleaseAsync(CancellationToken cancellationToken) { if (_connection is null) { @@ -79,6 +82,66 @@ public async Task StopAsync(CancellationToken cancellationToken) _connection = null; } + public override AsyncUnaryCall StartAsync( + StartRequest request, CallOptions options) + { + if (_startedReceiver is null) + { + throw new InvalidOperationException($"{nameof(NodeService)} is not initialized."); + } + + var call = base.StartAsync(request, options); + return new AsyncUnaryCall( + responseAsync: ResponseAsync(), + call.ResponseHeadersAsync, + call.GetStatus, + call.GetTrailers, + call.Dispose); + + async Task ResponseAsync() + { + await _startedReceiver.StopAsync(default); + try + { + return await call.ResponseAsync; + } + finally + { + await _startedReceiver.StartAsync(default); + } + } + } + + public override AsyncUnaryCall StopAsync( + StopRequest request, CallOptions options) + { + if (_stoppedReceiver is null) + { + throw new InvalidOperationException($"{nameof(NodeService)} is not initialized."); + } + + var call = base.StopAsync(request, options); + return new AsyncUnaryCall( + responseAsync: ResponseAsync(), + call.ResponseHeadersAsync, + call.GetStatus, + call.GetTrailers, + call.Dispose); + + async Task ResponseAsync() + { + await _stoppedReceiver.StopAsync(default); + try + { + return await call.ResponseAsync; + } + finally + { + await _stoppedReceiver.StartAsync(default); + } + } + } + private static async Task CheckConnectionAsync( NodeService nodeService, CancellationToken cancellationToken) { diff --git a/src/shared/LibplanetConsole.Node/NodeInfo.cs b/src/shared/LibplanetConsole.Node/NodeInfo.cs index d13a56c1..9198c31a 100644 --- a/src/shared/LibplanetConsole.Node/NodeInfo.cs +++ b/src/shared/LibplanetConsole.Node/NodeInfo.cs @@ -1,3 +1,4 @@ +using LibplanetConsole.Blockchain; using LibplanetConsole.Node.Grpc; namespace LibplanetConsole.Node; @@ -16,7 +17,7 @@ public readonly record struct NodeInfo public BlockHash GenesisHash { get; init; } - public BlockHash TipHash { get; init; } + public BlockInfo Tip { get; init; } public bool IsRunning { get; init; } @@ -26,6 +27,7 @@ public readonly record struct NodeInfo AppProtocolVersion = string.Empty, SwarmEndPoint = string.Empty, ConsensusEndPoint = string.Empty, + Tip = BlockInfo.Empty, }; public static implicit operator NodeInfo(NodeInformation nodeInfo) @@ -38,7 +40,12 @@ public static implicit operator NodeInfo(NodeInformation nodeInfo) ConsensusEndPoint = nodeInfo.ConsensusEndPoint, Address = new Address(nodeInfo.Address), GenesisHash = BlockHash.FromString(nodeInfo.GenesisHash), - TipHash = BlockHash.FromString(nodeInfo.TipHash), + Tip = new BlockInfo + { + Height = nodeInfo.TipHeight, + Hash = BlockHash.FromString(nodeInfo.TipHash), + Miner = new Address(nodeInfo.TipMiner), + }, IsRunning = nodeInfo.IsRunning, }; } @@ -53,7 +60,9 @@ public static implicit operator NodeInformation(NodeInfo nodeInfo) ConsensusEndPoint = nodeInfo.ConsensusEndPoint, Address = nodeInfo.Address.ToHex(), GenesisHash = nodeInfo.GenesisHash.ToString(), - TipHash = nodeInfo.TipHash.ToString(), + TipHash = nodeInfo.Tip.Hash.ToString(), + TipHeight = nodeInfo.Tip.Height, + TipMiner = nodeInfo.Tip.Miner.ToHex(), IsRunning = nodeInfo.IsRunning, }; } diff --git a/src/shared/LibplanetConsole.Node/Protos/NodeGrpcService.proto b/src/shared/LibplanetConsole.Node/Protos/NodeGrpcService.proto index eacdd1bc..48331c18 100644 --- a/src/shared/LibplanetConsole.Node/Protos/NodeGrpcService.proto +++ b/src/shared/LibplanetConsole.Node/Protos/NodeGrpcService.proto @@ -22,7 +22,9 @@ message NodeInformation { string address = 5; string genesis_hash = 6; string tip_hash = 7; - bool is_running = 8; + int64 tip_height = 8; + string tip_miner = 9; + bool is_running = 10; } message PingRequest { From 4271a4e71d375861c395872fe239d58b07a077de Mon Sep 17 00:00:00 2001 From: s2quake Date: Sun, 13 Oct 2024 18:15:39 +0900 Subject: [PATCH 13/18] refactor: Endpoint to port --- .../Application.cs | 3 +- .../ApplicationSettings.cs | 12 +-- .../EntryCommands/InitializeCommand.cs | 10 +- .../Repository.cs | 9 +- .../Tracers/ClientEventTracer.cs | 4 +- .../ApplicationInfo.cs | 3 +- .../ApplicationInfoProvider.cs | 2 +- .../ApplicationOptions.cs | 6 +- .../EndPointUtility.cs | 8 ++ .../LibplanetConsole.Common/PortUtility.cs | 2 +- src/common/LibplanetConsole.Seed/SeedNode.cs | 7 +- .../LibplanetConsole.Seed/SeedOptions.cs | 6 +- .../Application.cs | 3 +- .../ApplicationSettings.cs | 21 ++-- .../ClientRepositoryProcess.cs | 6 +- .../EntryCommands/InitializeCommand.cs | 97 ++++++++++++++----- .../EntryCommands/PortGenerationMode.cs | 8 ++ .../NodeRepositoryProcess.cs | 6 +- .../Repository.cs | 21 ++-- .../ApplicationInfo.cs | 6 +- .../ApplicationInfoProvider.cs | 2 +- .../ApplicationOptions.cs | 6 +- .../LibplanetConsole.Console/ClientOptions.cs | 7 +- .../LibplanetConsole.Console/ClientProcess.cs | 10 +- src/console/LibplanetConsole.Console/Node.cs | 7 +- .../LibplanetConsole.Console/NodeOptions.cs | 7 +- .../LibplanetConsole.Console/NodeProcess.cs | 10 +- .../LibplanetConsole.Console/SeedService.cs | 5 +- .../Application.cs | 3 +- .../ApplicationSettings.cs | 17 ++-- .../EntryCommands/InitializeCommand.cs | 15 +-- .../Repository.cs | 4 +- .../Tracers/NodeEventTracer.cs | 4 +- .../LibplanetConsole.Node/ApplicationInfo.cs | 3 +- .../ApplicationInfoProvider.cs | 2 +- .../ApplicationOptions.cs | 6 +- src/node/LibplanetConsole.Node/SeedService.cs | 5 +- .../ServiceCollectionExtensions.cs | 3 +- 38 files changed, 218 insertions(+), 138 deletions(-) create mode 100644 src/console/LibplanetConsole.Console.Executable/EntryCommands/PortGenerationMode.cs 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..3b6771ee 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 = 10; + 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()); From 2eb3f1a56446551298f4f1bff99ea8b1cd6dda35 Mon Sep 17 00:00:00 2001 From: s2quake Date: Sun, 13 Oct 2024 18:30:00 +0900 Subject: [PATCH 14/18] chore: Remove unused path --- .submodules/lib9c | 1 - 1 file changed, 1 deletion(-) delete mode 160000 .submodules/lib9c diff --git a/.submodules/lib9c b/.submodules/lib9c deleted file mode 160000 index 178eafae..00000000 --- a/.submodules/lib9c +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 178eafae4e054b283a6e198254bba4a85e8f6f6d From 9eb1cc91960496383776a4cfc7f523d354d53e2a Mon Sep 17 00:00:00 2001 From: s2quake Date: Sun, 13 Oct 2024 20:53:13 +0900 Subject: [PATCH 15/18] refactor: Send logs to the trace when the node is run by the RunCommand. --- .vscode/launch.json | 2 +- .../Application.cs | 10 ++++- .../EntryCommands/StartCommand.cs | 2 - .../LibplanetConsole.Client.Executable.csproj | 1 + .../Tracers/BlockChainEventTracer.cs | 1 - src/client/LibplanetConsole.Client/Client.cs | 1 - src/common/LibplanetConsole.Seed/SeedNode.cs | 4 +- .../Grpc/EvidenceChannel.cs | 2 - .../Application.cs | 10 ++++- .../EntryCommands/StartCommand.cs | 2 - ...LibplanetConsole.Console.Executable.csproj | 1 + .../ClientCollection.cs | 1 - .../LibplanetConsole.Console/SeedService.cs | 2 - .../Application.cs | 12 ++++- .../LibplanetConsole.Node.Executable.csproj | 1 + .../Tracers/BlockChainEventTracer.cs | 1 - src/node/LibplanetConsole.Node/SeedService.cs | 1 - .../LoggingExtensions.cs | 45 +++++++++++++++++++ 18 files changed, 79 insertions(+), 20 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 0de617bd..55d9125d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -135,7 +135,7 @@ ], "compounds": [ { - "name": "Node and Client", + "name": "Node and Client - Start", "configurations": [ "C#: Libplanet Node - Start", "C#: Libplanet Client - Start" diff --git a/src/client/LibplanetConsole.Client.Executable/Application.cs b/src/client/LibplanetConsole.Client.Executable/Application.cs index 6b4d01ee..fa0683f2 100644 --- a/src/client/LibplanetConsole.Client.Executable/Application.cs +++ b/src/client/LibplanetConsole.Client.Executable/Application.cs @@ -1,7 +1,6 @@ using JSSoft.Commands; using LibplanetConsole.Client.Executable.Commands; using LibplanetConsole.Client.Executable.Tracers; -using LibplanetConsole.Common; using LibplanetConsole.Logging; using Microsoft.AspNetCore.Server.Kestrel.Core; @@ -15,6 +14,11 @@ internal sealed class Application new PrefixFilter("app", "LibplanetConsole."), ]; + private readonly LoggingFilter[] _traceFilters = + [ + new PrefixFilter("app", "LibplanetConsole."), + ]; + public Application(ApplicationOptions options, object[] instances) { var port = options.Port; @@ -34,6 +38,10 @@ public Application(ApplicationOptions options, object[] instances) { services.AddLogging(options.LogPath, "client.log", _filters); } + else + { + services.AddLogging(_traceFilters); + } services.AddSingleton(); services.AddSingleton(); diff --git a/src/client/LibplanetConsole.Client.Executable/EntryCommands/StartCommand.cs b/src/client/LibplanetConsole.Client.Executable/EntryCommands/StartCommand.cs index 4671e488..f9039128 100644 --- a/src/client/LibplanetConsole.Client.Executable/EntryCommands/StartCommand.cs +++ b/src/client/LibplanetConsole.Client.Executable/EntryCommands/StartCommand.cs @@ -1,10 +1,8 @@ using System.ComponentModel; using JSSoft.Commands; -using LibplanetConsole.Common; using LibplanetConsole.DataAnnotations; using LibplanetConsole.Framework; using LibplanetConsole.Settings; -using Microsoft.AspNetCore.Server.Kestrel.Core; namespace LibplanetConsole.Client.Executable.EntryCommands; diff --git a/src/client/LibplanetConsole.Client.Executable/LibplanetConsole.Client.Executable.csproj b/src/client/LibplanetConsole.Client.Executable/LibplanetConsole.Client.Executable.csproj index c69322c2..c76fc9b5 100644 --- a/src/client/LibplanetConsole.Client.Executable/LibplanetConsole.Client.Executable.csproj +++ b/src/client/LibplanetConsole.Client.Executable/LibplanetConsole.Client.Executable.csproj @@ -11,6 +11,7 @@ + diff --git a/src/client/LibplanetConsole.Client.Executable/Tracers/BlockChainEventTracer.cs b/src/client/LibplanetConsole.Client.Executable/Tracers/BlockChainEventTracer.cs index 4bf1ef64..93f97890 100644 --- a/src/client/LibplanetConsole.Client.Executable/Tracers/BlockChainEventTracer.cs +++ b/src/client/LibplanetConsole.Client.Executable/Tracers/BlockChainEventTracer.cs @@ -1,4 +1,3 @@ -using JSSoft.Terminals; using LibplanetConsole.Blockchain; using LibplanetConsole.Common.Extensions; diff --git a/src/client/LibplanetConsole.Client/Client.cs b/src/client/LibplanetConsole.Client/Client.cs index e55bf3c6..4efe8a97 100644 --- a/src/client/LibplanetConsole.Client/Client.cs +++ b/src/client/LibplanetConsole.Client/Client.cs @@ -1,4 +1,3 @@ -using Grpc.Core; using Grpc.Net.Client; using LibplanetConsole.Blockchain; using LibplanetConsole.Blockchain.Grpc; diff --git a/src/common/LibplanetConsole.Seed/SeedNode.cs b/src/common/LibplanetConsole.Seed/SeedNode.cs index 19e7ea0a..f23dad85 100644 --- a/src/common/LibplanetConsole.Seed/SeedNode.cs +++ b/src/common/LibplanetConsole.Seed/SeedNode.cs @@ -1,9 +1,7 @@ -using System.Net; -using Dasync.Collections; +using Dasync.Collections; using Libplanet.Net.Messages; using Libplanet.Net.Options; using Libplanet.Net.Transports; -using LibplanetConsole.Common; using Serilog; using static LibplanetConsole.Common.EndPointUtility; diff --git a/src/console/LibplanetConsole.Console.Evidence/Grpc/EvidenceChannel.cs b/src/console/LibplanetConsole.Console.Evidence/Grpc/EvidenceChannel.cs index 3ccfdf7c..7ccefacd 100644 --- a/src/console/LibplanetConsole.Console.Evidence/Grpc/EvidenceChannel.cs +++ b/src/console/LibplanetConsole.Console.Evidence/Grpc/EvidenceChannel.cs @@ -1,6 +1,4 @@ -using Grpc.Core; using Grpc.Net.Client; -using Grpc.Net.Client.Configuration; using LibplanetConsole.Common; namespace LibplanetConsole.Evidence.Grpc; diff --git a/src/console/LibplanetConsole.Console.Executable/Application.cs b/src/console/LibplanetConsole.Console.Executable/Application.cs index 73eb9816..b3fda9d5 100644 --- a/src/console/LibplanetConsole.Console.Executable/Application.cs +++ b/src/console/LibplanetConsole.Console.Executable/Application.cs @@ -1,5 +1,4 @@ using JSSoft.Commands; -using LibplanetConsole.Common; using LibplanetConsole.Console.Evidence; using LibplanetConsole.Console.Executable.Commands; using LibplanetConsole.Console.Executable.Tracers; @@ -19,6 +18,11 @@ internal sealed class Application new PrefixFilter("seed.log", "LibplanetConsole.Seed."), ]; + private readonly LoggingFilter[] _traceFilters = + [ + new PrefixFilter("app", "LibplanetConsole."), + ]; + public Application(ApplicationOptions options, object[] instances) { var port = options.Port; @@ -39,6 +43,10 @@ public Application(ApplicationOptions options, object[] instances) { services.AddLogging(options.LogPath, "console.log", _filters); } + else + { + services.AddLogging(_traceFilters); + } services.AddSingleton(); services.AddSingleton(); diff --git a/src/console/LibplanetConsole.Console.Executable/EntryCommands/StartCommand.cs b/src/console/LibplanetConsole.Console.Executable/EntryCommands/StartCommand.cs index f57fa8b3..3b4a1b9e 100644 --- a/src/console/LibplanetConsole.Console.Executable/EntryCommands/StartCommand.cs +++ b/src/console/LibplanetConsole.Console.Executable/EntryCommands/StartCommand.cs @@ -1,9 +1,7 @@ using JSSoft.Commands; -using LibplanetConsole.Common; using LibplanetConsole.DataAnnotations; using LibplanetConsole.Framework; using LibplanetConsole.Settings; -using Microsoft.AspNetCore.Server.Kestrel.Core; namespace LibplanetConsole.Console.Executable.EntryCommands; diff --git a/src/console/LibplanetConsole.Console.Executable/LibplanetConsole.Console.Executable.csproj b/src/console/LibplanetConsole.Console.Executable/LibplanetConsole.Console.Executable.csproj index 020bd0a4..e152f23e 100644 --- a/src/console/LibplanetConsole.Console.Executable/LibplanetConsole.Console.Executable.csproj +++ b/src/console/LibplanetConsole.Console.Executable/LibplanetConsole.Console.Executable.csproj @@ -9,6 +9,7 @@ + diff --git a/src/console/LibplanetConsole.Console/ClientCollection.cs b/src/console/LibplanetConsole.Console/ClientCollection.cs index 64e9b26a..4aff8d55 100644 --- a/src/console/LibplanetConsole.Console/ClientCollection.cs +++ b/src/console/LibplanetConsole.Console/ClientCollection.cs @@ -3,7 +3,6 @@ using LibplanetConsole.Common.Extensions; using LibplanetConsole.Framework; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace LibplanetConsole.Console; diff --git a/src/console/LibplanetConsole.Console/SeedService.cs b/src/console/LibplanetConsole.Console/SeedService.cs index d1943859..0816c091 100644 --- a/src/console/LibplanetConsole.Console/SeedService.cs +++ b/src/console/LibplanetConsole.Console/SeedService.cs @@ -1,7 +1,5 @@ using LibplanetConsole.Common; using LibplanetConsole.Seed; -using Microsoft.Extensions.Hosting; -using static LibplanetConsole.Common.EndPointUtility; namespace LibplanetConsole.Console; diff --git a/src/node/LibplanetConsole.Node.Executable/Application.cs b/src/node/LibplanetConsole.Node.Executable/Application.cs index 788f3cd1..c9801b00 100644 --- a/src/node/LibplanetConsole.Node.Executable/Application.cs +++ b/src/node/LibplanetConsole.Node.Executable/Application.cs @@ -1,5 +1,4 @@ using JSSoft.Commands; -using LibplanetConsole.Common; using LibplanetConsole.Logging; using LibplanetConsole.Node.Evidence; using LibplanetConsole.Node.Executable.Commands; @@ -21,6 +20,13 @@ internal sealed class Application new PrefixFilter("libplanet.log", "Libplanet."), ]; + private readonly LoggingFilter[] _traceFilters = + [ + new SourceContextFilter( + "app.log", + s => s.StartsWith("LibplanetConsole.") && !s.StartsWith("LibplanetConsole.Seed.")), + ]; + public Application(ApplicationOptions options, object[] instances) { var port = options.Port; @@ -40,6 +46,10 @@ public Application(ApplicationOptions options, object[] instances) { services.AddLogging(options.LogPath, "node.log", _filters); } + else + { + services.AddLogging(_traceFilters); + } services.AddSingleton(); services.AddSingleton(); diff --git a/src/node/LibplanetConsole.Node.Executable/LibplanetConsole.Node.Executable.csproj b/src/node/LibplanetConsole.Node.Executable/LibplanetConsole.Node.Executable.csproj index ed3f7fb1..07078ed2 100644 --- a/src/node/LibplanetConsole.Node.Executable/LibplanetConsole.Node.Executable.csproj +++ b/src/node/LibplanetConsole.Node.Executable/LibplanetConsole.Node.Executable.csproj @@ -9,6 +9,7 @@ + diff --git a/src/node/LibplanetConsole.Node.Executable/Tracers/BlockChainEventTracer.cs b/src/node/LibplanetConsole.Node.Executable/Tracers/BlockChainEventTracer.cs index 3d0c23d1..9d359ae3 100644 --- a/src/node/LibplanetConsole.Node.Executable/Tracers/BlockChainEventTracer.cs +++ b/src/node/LibplanetConsole.Node.Executable/Tracers/BlockChainEventTracer.cs @@ -1,4 +1,3 @@ -using JSSoft.Terminals; using LibplanetConsole.Blockchain; using LibplanetConsole.Common.Extensions; diff --git a/src/node/LibplanetConsole.Node/SeedService.cs b/src/node/LibplanetConsole.Node/SeedService.cs index b72cfd2a..e96266c5 100644 --- a/src/node/LibplanetConsole.Node/SeedService.cs +++ b/src/node/LibplanetConsole.Node/SeedService.cs @@ -1,6 +1,5 @@ using LibplanetConsole.Common; using LibplanetConsole.Seed; -using static LibplanetConsole.Common.EndPointUtility; namespace LibplanetConsole.Node; diff --git a/src/shared/LibplanetConsole.Logging/LoggingExtensions.cs b/src/shared/LibplanetConsole.Logging/LoggingExtensions.cs index 3f1f55f1..d76300f9 100644 --- a/src/shared/LibplanetConsole.Logging/LoggingExtensions.cs +++ b/src/shared/LibplanetConsole.Logging/LoggingExtensions.cs @@ -7,6 +7,21 @@ internal static class LoggingExtensions public static IServiceCollection AddLogging( this IServiceCollection @this, string logPath, string name, params LoggingFilter[] filters) { + if (logPath == string.Empty) + { + throw new ArgumentException("Log path must be specified.", nameof(logPath)); + } + + if (File.Exists(logPath) is true) + { + throw new ArgumentException("Log path must be a directory.", nameof(logPath)); + } + + if (name == string.Empty) + { + throw new ArgumentException("Name must be specified.", nameof(name)); + } + var loggerConfiguration = new LoggerConfiguration(); var logFilename = Path.Combine(logPath, name); loggerConfiguration = loggerConfiguration.MinimumLevel.Debug(); @@ -37,4 +52,34 @@ public static IServiceCollection AddLogging( return @this; } + + public static IServiceCollection AddLogging( + this IServiceCollection @this, params LoggingFilter[] filters) + { + var loggerConfiguration = new LoggerConfiguration(); + loggerConfiguration = loggerConfiguration.MinimumLevel.Debug(); + + foreach (var filter in filters) + { + loggerConfiguration = loggerConfiguration + .WriteTo.Logger(lc => lc + .Filter.ByIncludingOnly(filter.Filter) + .WriteTo.Trace()); + } + + Log.Logger = loggerConfiguration.CreateLogger(); + AppDomain.CurrentDomain.UnhandledException += (_, e) => + { + Log.Logger.Fatal(e.ExceptionObject as Exception, "Unhandled exception occurred."); + }; + + @this.AddSingleton(); + @this.AddLogging(builder => + { + builder.ClearProviders(); + builder.AddSerilog(); + }); + + return @this; + } } From fe44d70dabc435138d0da6f47ddb13bbed96776e Mon Sep 17 00:00:00 2001 From: s2quake Date: Sun, 13 Oct 2024 21:43:12 +0900 Subject: [PATCH 16/18] wip --- .../PortGenerationMode.cs | 2 +- .../LibplanetConsole.Common/PortGenerator.cs | 60 +++++++++++++++++ .../ApplicationSettings.cs | 15 +++-- .../EntryCommands/InitializeCommand.cs | 65 ++++--------------- .../Tracers/ClientCollectionEventTracer.cs | 13 ++-- .../Tracers/NodeCollectionEventTracer.cs | 25 +++++-- .../ApplicationOptions.cs | 3 + src/console/LibplanetConsole.Console/Node.cs | 10 --- .../LibplanetConsole.Console/SeedService.cs | 6 +- .../ApplicationOptions.cs | 5 ++ src/node/LibplanetConsole.Node/Node.cs | 44 ++++--------- src/node/LibplanetConsole.Node/SeedService.cs | 6 +- src/shared/LibplanetConsole.Node/NodeInfo.cs | 14 ++-- .../Protos/NodeGrpcService.proto | 4 +- 14 files changed, 145 insertions(+), 127 deletions(-) rename src/{console/LibplanetConsole.Console.Executable/EntryCommands => common/LibplanetConsole.Common}/PortGenerationMode.cs (51%) create mode 100644 src/common/LibplanetConsole.Common/PortGenerator.cs diff --git a/src/console/LibplanetConsole.Console.Executable/EntryCommands/PortGenerationMode.cs b/src/common/LibplanetConsole.Common/PortGenerationMode.cs similarity index 51% rename from src/console/LibplanetConsole.Console.Executable/EntryCommands/PortGenerationMode.cs rename to src/common/LibplanetConsole.Common/PortGenerationMode.cs index 1a7dcf8a..60ec74bb 100644 --- a/src/console/LibplanetConsole.Console.Executable/EntryCommands/PortGenerationMode.cs +++ b/src/common/LibplanetConsole.Common/PortGenerationMode.cs @@ -1,4 +1,4 @@ -namespace LibplanetConsole.Console.Executable.EntryCommands; +namespace LibplanetConsole.Common; public enum PortGenerationMode { diff --git a/src/common/LibplanetConsole.Common/PortGenerator.cs b/src/common/LibplanetConsole.Common/PortGenerator.cs new file mode 100644 index 00000000..f40006c9 --- /dev/null +++ b/src/common/LibplanetConsole.Common/PortGenerator.cs @@ -0,0 +1,60 @@ +using System.Net.Sockets; +using LibplanetConsole.Common.DataAnnotations; + +namespace LibplanetConsole.Common; + +public sealed class PortGenerator +{ + public const int DefaultSpace = 10; + private readonly List _portList = []; + + public PortGenerator(int startingPort) + { + _portList.Add(startingPort == 0 ? PortUtility.NextPort() : startingPort); + Current = _portList[0]; + } + + public int Space { get; init; } = DefaultSpace; + + public int Current { get; private set; } + + public PortGenerationMode Mode { get; init; } = PortGenerationMode.Random; + + public int Next() + { + Current = GetPort(Current); + _portList.Add(Current); + _portList.Sort(); + return Current; + } + + private int GetPort(int nextPort) + { + if (Mode == PortGenerationMode.Random) + { + var port = PortUtility.NextPort(); + while (IsValidRandomPort(port) is false) + { + port = PortUtility.NextPort(); + } + + return port; + } + + return nextPort + Space; + } + + private bool IsValidRandomPort(int randomPort) + { + for (var i = 0; i < _portList.Count; i++) + { + var port = _portList[i]; + if (Math.Abs(port - randomPort) < Space) + { + return false; + } + } + + return true; + } +} diff --git a/src/console/LibplanetConsole.Console.Executable/ApplicationSettings.cs b/src/console/LibplanetConsole.Console.Executable/ApplicationSettings.cs index 4964a35e..7057f04c 100644 --- a/src/console/LibplanetConsole.Console.Executable/ApplicationSettings.cs +++ b/src/console/LibplanetConsole.Console.Executable/ApplicationSettings.cs @@ -91,10 +91,11 @@ internal sealed record class ApplicationSettings public ApplicationOptions ToOptions() { - var port = Port == 0 ? PortUtility.NextPort() : Port; + var portGenerator = new PortGenerator(Port); + var port = portGenerator.Current; var endPoint = GetLocalHost(port); - var nodeOptions = GetNodeOptions(endPoint, GetNodes()); - var clientOptions = GetClientOptions(nodeOptions, GetClients()); + var nodeOptions = GetNodeOptions(endPoint, GetNodes(), portGenerator); + var clientOptions = GetClientOptions(nodeOptions, GetClients(), portGenerator); var repository = new Repository(port, nodeOptions, clientOptions); var genesis = TryGetGenesis(out var g) == true ? g : repository.Genesis; return new ApplicationOptions(port) @@ -130,22 +131,22 @@ public static ApplicationSettings Parse(string[] args) } private static NodeOptions[] GetNodeOptions( - EndPoint endPoint, PrivateKey[] nodePrivateKeys) + EndPoint endPoint, PrivateKey[] nodePrivateKeys, PortGenerator portGenerator) { return [.. nodePrivateKeys.Select(key => new NodeOptions { - EndPoint = NextEndPoint(), + EndPoint = GetLocalHost(portGenerator.Next()), PrivateKey = key, SeedEndPoint = endPoint, })]; } private static ClientOptions[] GetClientOptions( - NodeOptions[] nodeOptions, PrivateKey[] clientPrivateKeys) + NodeOptions[] nodeOptions, PrivateKey[] clientPrivateKeys, PortGenerator portGenerator) { return [.. clientPrivateKeys.Select(key => new ClientOptions { - EndPoint = NextEndPoint(), + EndPoint = GetLocalHost(portGenerator.Next()), NodeEndPoint = Random(nodeOptions).EndPoint, PrivateKey = key, })]; diff --git a/src/console/LibplanetConsole.Console.Executable/EntryCommands/InitializeCommand.cs b/src/console/LibplanetConsole.Console.Executable/EntryCommands/InitializeCommand.cs index 3b6771ee..9b1e0be3 100644 --- a/src/console/LibplanetConsole.Console.Executable/EntryCommands/InitializeCommand.cs +++ b/src/console/LibplanetConsole.Console.Executable/EntryCommands/InitializeCommand.cs @@ -14,9 +14,6 @@ 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 = 10; - private readonly List _portList = []; - public InitializeCommand() : base("init") { @@ -95,13 +92,13 @@ 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. " + + [CommandProperty("port-spacing", InitValue = PortGenerator.DefaultSpace)] + [CommandSummary("Specifies the spacing between ports. Default is 10. " + "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'")] + "the value of this option is 10'")] [Category("Network")] - [Range(1, 10000)] + [Range(10, 10000)] public int PortSpacing { get; set; } [CommandProperty("port-generation-mode")] @@ -111,11 +108,11 @@ public InitializeCommand() protected override void OnExecute() { + var portGenerator = new PortGenerator(Port); var genesisKey = PrivateKeyUtility.ParseOrRandom(GenesisKey); - var port = Port == 0 ? PortUtility.NextPort() : Port; - var nextPort = port; - var nodeOptions = GetNodeOptions(ref nextPort); - var clientOptions = GetClientOptions(ref nextPort); + var port = portGenerator.Current; + var nodeOptions = GetNodeOptions(portGenerator); + var clientOptions = GetClientOptions(portGenerator); var outputPath = Path.GetFullPath(RepositoryPath); var dateTimeOffset = DateTimeOffset != DateTimeOffset.MinValue ? DateTimeOffset : DateTimeOffset.UtcNow; @@ -152,45 +149,7 @@ protected override void OnExecute() TextWriterExtensions.WriteLineAsJson(writer, info); } - 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) + private NodeOptions[] GetNodeOptions(PortGenerator portGenerator) { var privateKeys = GetNodes(); var nodeOptionsList = new List(privateKeys.Length); @@ -198,7 +157,7 @@ private NodeOptions[] GetNodeOptions(ref int nextPort) { var nodeOptions = new NodeOptions { - EndPoint = GetLocalHost(NextPort(ref nextPort)), + EndPoint = GetLocalHost(portGenerator.Next()), PrivateKey = privateKey, StorePath = "store", LogPath = "log", @@ -211,7 +170,7 @@ private NodeOptions[] GetNodeOptions(ref int nextPort) return [.. nodeOptionsList]; } - private ClientOptions[] GetClientOptions(ref int nextPort) + private ClientOptions[] GetClientOptions(PortGenerator portGenerator) { var privateKeys = GetClients(); var clientOptionsList = new List(privateKeys.Length); @@ -219,7 +178,7 @@ private ClientOptions[] GetClientOptions(ref int nextPort) { var clientOptions = new ClientOptions { - EndPoint = GetLocalHost(NextPort(ref nextPort)), + EndPoint = GetLocalHost(portGenerator.Next()), PrivateKey = privateKey, LogPath = "log", }; diff --git a/src/console/LibplanetConsole.Console.Executable/Tracers/ClientCollectionEventTracer.cs b/src/console/LibplanetConsole.Console.Executable/Tracers/ClientCollectionEventTracer.cs index bb7860aa..b1e750f5 100644 --- a/src/console/LibplanetConsole.Console.Executable/Tracers/ClientCollectionEventTracer.cs +++ b/src/console/LibplanetConsole.Console.Executable/Tracers/ClientCollectionEventTracer.cs @@ -7,6 +7,7 @@ namespace LibplanetConsole.Console.Executable.Tracers; internal sealed class ClientCollectionEventTracer : IHostedService, IDisposable { private readonly IClientCollection _clients; + private bool _isDisposed; public ClientCollectionEventTracer(IClientCollection clients) { @@ -27,12 +28,16 @@ public Task StopAsync(CancellationToken cancellationToken) void IDisposable.Dispose() { - foreach (var client in _clients) + if (_isDisposed is false) { - DetachEvent(client); - } + foreach (var client in _clients) + { + DetachEvent(client); + } - _clients.CollectionChanged -= Clients_CollectionChanged; + _clients.CollectionChanged -= Clients_CollectionChanged; + _isDisposed = true; + } } private void AttachEvent(IClient client) diff --git a/src/console/LibplanetConsole.Console.Executable/Tracers/NodeCollectionEventTracer.cs b/src/console/LibplanetConsole.Console.Executable/Tracers/NodeCollectionEventTracer.cs index f3390aa9..3e3837f4 100644 --- a/src/console/LibplanetConsole.Console.Executable/Tracers/NodeCollectionEventTracer.cs +++ b/src/console/LibplanetConsole.Console.Executable/Tracers/NodeCollectionEventTracer.cs @@ -10,6 +10,7 @@ internal sealed class NodeCollectionEventTracer : IHostedService, IDisposable private readonly INodeCollection _nodes; private readonly ILogger _logger; private INode? _current; + private bool _isDisposed; public NodeCollectionEventTracer( INodeCollection nodes, ILogger logger) @@ -34,12 +35,16 @@ public Task StopAsync(CancellationToken cancellationToken) void IDisposable.Dispose() { - _nodes.CurrentChanged -= Nodes_CurrentChanged; - _nodes.CollectionChanged -= Nodes_CollectionChanged; - UpdateCurrent(null); - foreach (var node in _nodes) + if (_isDisposed is false) { - DetachEvent(node); + _nodes.CurrentChanged -= Nodes_CurrentChanged; + _nodes.CollectionChanged -= Nodes_CollectionChanged; + foreach (var node in _nodes) + { + DetachEvent(node); + } + + _isDisposed = true; } } @@ -64,6 +69,7 @@ private void AttachEvent(INode node) node.Detached += Node_Detached; node.Started += Node_Started; node.Stopped += Node_Stopped; + node.Disposed += Node_Disposed; } private void DetachEvent(INode node) @@ -72,6 +78,7 @@ private void DetachEvent(INode node) node.Detached -= Node_Detached; node.Started -= Node_Started; node.Stopped -= Node_Stopped; + node.Disposed += Node_Disposed; } private void Nodes_CurrentChanged(object? sender, EventArgs e) @@ -154,4 +161,12 @@ private void Node_Stopped(object? sender, EventArgs e) System.Console.Out.WriteColoredLine(message, colorType); } } + + private void Node_Disposed(object? sender, EventArgs e) + { + if (sender is INode node && node == _current) + { + _current = null; + } + } } diff --git a/src/console/LibplanetConsole.Console/ApplicationOptions.cs b/src/console/LibplanetConsole.Console/ApplicationOptions.cs index ca843d1f..53407b52 100644 --- a/src/console/LibplanetConsole.Console/ApplicationOptions.cs +++ b/src/console/LibplanetConsole.Console/ApplicationOptions.cs @@ -2,6 +2,9 @@ namespace LibplanetConsole.Console; public sealed record class ApplicationOptions { + public const int SeedBlocksyncPortIncrement = 6; + public const int SeedConsensusPortIncrement = 7; + public ApplicationOptions(int port) { Port = port; diff --git a/src/console/LibplanetConsole.Console/Node.cs b/src/console/LibplanetConsole.Console/Node.cs index 690b1ee5..9c6b5e4b 100644 --- a/src/console/LibplanetConsole.Console/Node.cs +++ b/src/console/LibplanetConsole.Console/Node.cs @@ -24,8 +24,6 @@ internal sealed partial class Node : INode, IBlockChain private NodeService? _nodeService; private BlockChainService? _blockChainService; private GrpcChannel? _channel; - private EndPoint? _blocksyncEndPoint; - private EndPoint? _consensusEndPoint; private NodeInfo _nodeInfo; private bool _isDisposed; private NodeProcess? _process; @@ -52,12 +50,6 @@ public Node(IServiceProvider serviceProvider, NodeOptions nodeOptions) public event EventHandler? Disposed; - public EndPoint SwarmEndPoint - => _blocksyncEndPoint ?? throw new InvalidOperationException("Peer is not set."); - - public EndPoint ConsensusEndPoint - => _consensusEndPoint ?? throw new InvalidOperationException("ConsensusPeer is not set."); - public PublicKey PublicKey { get; } public Address Address => PublicKey.Address; @@ -210,8 +202,6 @@ public async Task StartAsync(CancellationToken cancellationToken) var callOptions = new CallOptions(cancellationToken: cancellationToken); var response = await _nodeService.StartAsync(request, callOptions); _nodeInfo = response.NodeInfo; - _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/SeedService.cs b/src/console/LibplanetConsole.Console/SeedService.cs index 0816c091..58a11893 100644 --- a/src/console/LibplanetConsole.Console/SeedService.cs +++ b/src/console/LibplanetConsole.Console/SeedService.cs @@ -3,7 +3,7 @@ namespace LibplanetConsole.Console; -internal sealed class SeedService : ISeedService +internal sealed class SeedService(ApplicationOptions options) : ISeedService { private readonly PrivateKey _seedNodePrivateKey = new(); private SeedNode? _blocksyncSeedNode; @@ -33,12 +33,12 @@ public async Task StartAsync(CancellationToken cancellationToken) _blocksyncSeedNode = new SeedNode(new() { PrivateKey = _seedNodePrivateKey, - Port = PortUtility.NextPort(), + Port = options.Port + ApplicationOptions.SeedBlocksyncPortIncrement, }); _consensusSeedNode = new SeedNode(new() { PrivateKey = _seedNodePrivateKey, - Port = PortUtility.NextPort(), + Port = options.Port + ApplicationOptions.SeedConsensusPortIncrement, }); await _blocksyncSeedNode.StartAsync(cancellationToken); await _consensusSeedNode.StartAsync(cancellationToken); diff --git a/src/node/LibplanetConsole.Node/ApplicationOptions.cs b/src/node/LibplanetConsole.Node/ApplicationOptions.cs index bcf1bb99..372eb224 100644 --- a/src/node/LibplanetConsole.Node/ApplicationOptions.cs +++ b/src/node/LibplanetConsole.Node/ApplicationOptions.cs @@ -2,6 +2,11 @@ namespace LibplanetConsole.Node; public sealed record class ApplicationOptions { + public const int BlocksyncPortIncrement = 4; + public const int ConsensusPortIncrement = 5; + public const int SeedBlocksyncPortIncrement = 6; + public const int SeedConsensusPortIncrement = 7; + public ApplicationOptions(int port, PrivateKey privateKey, byte[] genesis) { Port = port; diff --git a/src/node/LibplanetConsole.Node/Node.cs b/src/node/LibplanetConsole.Node/Node.cs index f49cbbee..01fed2cf 100644 --- a/src/node/LibplanetConsole.Node/Node.cs +++ b/src/node/LibplanetConsole.Node/Node.cs @@ -32,10 +32,10 @@ private readonly SynchronizationContext _synchronizationContext private readonly byte[] _genesis; private readonly AppProtocolVersion _appProtocolVersion = SeedNode.AppProtocolVersion; private readonly IActionProvider _actionProvider; + private readonly int _blocksyncPort; + private readonly int _consensusPort; private EndPoint? _seedEndPoint; - private EndPoint? _blocksyncEndPoint; - private EndPoint? _consensusEndPoint; private Swarm? _swarm; private Task _startTask = Task.CompletedTask; private bool _isDisposed; @@ -50,6 +50,8 @@ public Node(IServiceProvider serviceProvider, ApplicationOptions options) _actionProvider = options.ActionProvider ?? ActionProvider.Default; _logger = serviceProvider.GetLogger(); _genesis = options.Genesis; + _blocksyncPort = options.Port + ApplicationOptions.BlocksyncPortIncrement; + _consensusPort = options.Port + ApplicationOptions.ConsensusPortIncrement; UpdateNodeInfo(); _logger.LogDebug("Node is created: {Address}", Address); } @@ -58,18 +60,6 @@ public Node(IServiceProvider serviceProvider, ApplicationOptions options) public event EventHandler? Stopped; - public EndPoint SwarmEndPoint - { - get => _blocksyncEndPoint ?? throw new InvalidOperationException(); - set => _blocksyncEndPoint = value; - } - - public EndPoint ConsensusEndPoint - { - get => _consensusEndPoint ?? throw new InvalidOperationException(); - set => _consensusEndPoint = value; - } - public string StorePath => _storePath; public bool IsRunning { get; private set; } @@ -152,12 +142,12 @@ public async Task StartAsync(CancellationToken cancellationToken) var privateKey = PrivateKeyUtility.FromSecureString(_privateKey); var appProtocolVersion = _appProtocolVersion; var storePath = _storePath; - var blocksyncEndPoint = _blocksyncEndPoint ?? EndPointUtility.NextEndPoint(); - var consensusEndPoint = _consensusEndPoint ?? EndPointUtility.NextEndPoint(); + var blocksyncPort = _blocksyncPort; + var consensusPort = _consensusPort; var blocksyncSeedPeer = seedInfo.BlocksyncSeedPeer; var consensusSeedPeer = seedInfo.ConsensusSeedPeer; var swarmTransport - = await CreateTransport(privateKey, blocksyncEndPoint, appProtocolVersion); + = await CreateTransport(privateKey, blocksyncPort, appProtocolVersion); var swarmOptions = new SwarmOptions { StaticPeers = [blocksyncSeedPeer], @@ -168,12 +158,12 @@ var swarmTransport }; var consensusTransport = await CreateTransport( privateKey: privateKey, - endPoint: consensusEndPoint, + port: consensusPort, appProtocolVersion: appProtocolVersion); var consensusReactorOption = new ConsensusReactorOption { SeedPeers = [consensusSeedPeer], - ConsensusPort = EndPointUtility.GetHostAndPort(consensusEndPoint).Port, + ConsensusPort = consensusPort, ConsensusPrivateKey = privateKey, TargetBlockInterval = TimeSpan.FromSeconds(2), ContextTimeoutOptions = new(), @@ -184,8 +174,6 @@ var swarmTransport renderer: this, actionProvider: _actionProvider); - _blocksyncEndPoint = blocksyncEndPoint; - _consensusEndPoint = consensusEndPoint; _swarm = new Swarm( blockChain: blockChain, privateKey: privateKey, @@ -220,8 +208,6 @@ public async Task StopAsync(CancellationToken cancellationToken) _logger.LogDebug("Node.Swarm is stopped: {Address}", Address); } - _blocksyncEndPoint = null; - _consensusEndPoint = null; _swarm = null; _startTask = Task.CompletedTask; IsRunning = false; @@ -234,9 +220,6 @@ public async ValueTask DisposeAsync() { if (_isDisposed is false) { - _blocksyncEndPoint = null; - _consensusEndPoint = null; - if (_swarm is not null) { await _swarm.StopAsync(cancellationToken: default); @@ -284,14 +267,13 @@ void Action(object? state) } private static async Task CreateTransport( - PrivateKey privateKey, EndPoint endPoint, AppProtocolVersion appProtocolVersion) + PrivateKey privateKey, int port, AppProtocolVersion appProtocolVersion) { var appProtocolVersionOptions = new AppProtocolVersionOptions { AppProtocolVersion = appProtocolVersion, }; - var (host, port) = EndPointUtility.GetHostAndPort(endPoint); - var hostOptions = new HostOptions(host, [], port); + var hostOptions = new HostOptions("localhost", [], port); return await NetMQTransport.Create(privateKey, appProtocolVersionOptions, hostOptions); } @@ -328,14 +310,14 @@ private void UpdateNodeInfo() ProcessId = Environment.ProcessId, Address = Address, AppProtocolVersion = $"{appProtocolVersion}", + BlocksyncPort = _blocksyncPort, + ConsensusPort = _consensusPort, }; if (IsRunning == true) { nodeInfo = nodeInfo with { - SwarmEndPoint = EndPointUtility.ToString(SwarmEndPoint), - ConsensusEndPoint = EndPointUtility.ToString(ConsensusEndPoint), GenesisHash = BlockChain.Genesis.Hash, Tip = new BlockInfo(BlockChain.Tip), IsRunning = IsRunning, diff --git a/src/node/LibplanetConsole.Node/SeedService.cs b/src/node/LibplanetConsole.Node/SeedService.cs index e96266c5..02fdffce 100644 --- a/src/node/LibplanetConsole.Node/SeedService.cs +++ b/src/node/LibplanetConsole.Node/SeedService.cs @@ -3,7 +3,7 @@ namespace LibplanetConsole.Node; -internal sealed class SeedService : ISeedService +internal sealed class SeedService(ApplicationOptions options) : ISeedService { private readonly PrivateKey _seedNodePrivateKey = new(); private SeedNode? _blocksyncSeedNode; @@ -35,12 +35,12 @@ public async Task StartAsync(CancellationToken cancellationToken) var blocksyncSeedNode = new SeedNode(new() { PrivateKey = _seedNodePrivateKey, - Port = PortUtility.NextPort(), + Port = options.Port + ApplicationOptions.SeedBlocksyncPortIncrement, }); var consensusSeedNode = new SeedNode(new() { PrivateKey = _seedNodePrivateKey, - Port = PortUtility.NextPort(), + Port = options.Port + ApplicationOptions.SeedConsensusPortIncrement, }); await blocksyncSeedNode.StartAsync(cancellationToken); await consensusSeedNode.StartAsync(cancellationToken); diff --git a/src/shared/LibplanetConsole.Node/NodeInfo.cs b/src/shared/LibplanetConsole.Node/NodeInfo.cs index 9198c31a..18a9047b 100644 --- a/src/shared/LibplanetConsole.Node/NodeInfo.cs +++ b/src/shared/LibplanetConsole.Node/NodeInfo.cs @@ -9,9 +9,9 @@ public readonly record struct NodeInfo public string AppProtocolVersion { get; init; } - public string SwarmEndPoint { get; init; } + public int BlocksyncPort { get; init; } - public string ConsensusEndPoint { get; init; } + public int ConsensusPort { get; init; } public Address Address { get; init; } @@ -25,8 +25,6 @@ public readonly record struct NodeInfo { ProcessId = -1, AppProtocolVersion = string.Empty, - SwarmEndPoint = string.Empty, - ConsensusEndPoint = string.Empty, Tip = BlockInfo.Empty, }; @@ -36,8 +34,8 @@ public static implicit operator NodeInfo(NodeInformation nodeInfo) { ProcessId = nodeInfo.ProcessId, AppProtocolVersion = nodeInfo.AppProtocolVersion, - SwarmEndPoint = nodeInfo.SwarmEndPoint, - ConsensusEndPoint = nodeInfo.ConsensusEndPoint, + BlocksyncPort = nodeInfo.BlocksyncPort, + ConsensusPort = nodeInfo.ConsensusPort, Address = new Address(nodeInfo.Address), GenesisHash = BlockHash.FromString(nodeInfo.GenesisHash), Tip = new BlockInfo @@ -56,8 +54,8 @@ public static implicit operator NodeInformation(NodeInfo nodeInfo) { ProcessId = nodeInfo.ProcessId, AppProtocolVersion = nodeInfo.AppProtocolVersion, - SwarmEndPoint = nodeInfo.SwarmEndPoint, - ConsensusEndPoint = nodeInfo.ConsensusEndPoint, + BlocksyncPort = nodeInfo.BlocksyncPort, + ConsensusPort = nodeInfo.ConsensusPort, Address = nodeInfo.Address.ToHex(), GenesisHash = nodeInfo.GenesisHash.ToString(), TipHash = nodeInfo.Tip.Hash.ToString(), diff --git a/src/shared/LibplanetConsole.Node/Protos/NodeGrpcService.proto b/src/shared/LibplanetConsole.Node/Protos/NodeGrpcService.proto index 48331c18..3f9a075b 100644 --- a/src/shared/LibplanetConsole.Node/Protos/NodeGrpcService.proto +++ b/src/shared/LibplanetConsole.Node/Protos/NodeGrpcService.proto @@ -17,8 +17,8 @@ service NodeGrpcService { message NodeInformation { int32 process_id = 1; string app_protocol_version = 2; - string swarm_end_point = 3; - string consensus_end_point = 4; + int32 blocksync_port = 3; + int32 consensus_port = 4; string address = 5; string genesis_hash = 6; string tip_hash = 7; From 1cfdce28ef5b1c6c492187a6eb8c6c23708ca74c Mon Sep 17 00:00:00 2001 From: s2quake Date: Sun, 13 Oct 2024 22:11:17 +0900 Subject: [PATCH 17/18] fix: Fix error when the node and the client shutdown --- .../Threading/TaskUtility.cs | 13 +++++++++++++ src/console/LibplanetConsole.Console/Client.cs | 3 ++- src/console/LibplanetConsole.Console/Node.cs | 3 ++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/common/LibplanetConsole.Common/Threading/TaskUtility.cs b/src/common/LibplanetConsole.Common/Threading/TaskUtility.cs index 289dfe9a..80a087de 100644 --- a/src/common/LibplanetConsole.Common/Threading/TaskUtility.cs +++ b/src/common/LibplanetConsole.Common/Threading/TaskUtility.cs @@ -29,4 +29,17 @@ public static async Task TryDelay( return false; } } + + public static async Task TryWait(Task task) + { + try + { + await task; + return true; + } + catch (Exception) + { + return false; + } + } } diff --git a/src/console/LibplanetConsole.Console/Client.cs b/src/console/LibplanetConsole.Console/Client.cs index 45ccd699..4fe80a0d 100644 --- a/src/console/LibplanetConsole.Console/Client.cs +++ b/src/console/LibplanetConsole.Console/Client.cs @@ -6,6 +6,7 @@ using LibplanetConsole.Client.Grpc; using LibplanetConsole.Common; using LibplanetConsole.Common.Extensions; +using LibplanetConsole.Common.Threading; using LibplanetConsole.Console.Grpc; using Microsoft.Extensions.Logging; @@ -215,7 +216,7 @@ public async ValueTask DisposeAsync() { await _processCancellationTokenSource.CancelAsync(); _processCancellationTokenSource.Dispose(); - await _processTask; + await TaskUtility.TryWait(_processTask); _processTask = Task.CompletedTask; _process = null; diff --git a/src/console/LibplanetConsole.Console/Node.cs b/src/console/LibplanetConsole.Console/Node.cs index 9c6b5e4b..16eae484 100644 --- a/src/console/LibplanetConsole.Console/Node.cs +++ b/src/console/LibplanetConsole.Console/Node.cs @@ -4,6 +4,7 @@ using LibplanetConsole.Blockchain.Grpc; using LibplanetConsole.Common; using LibplanetConsole.Common.Extensions; +using LibplanetConsole.Common.Threading; using LibplanetConsole.Console.Grpc; using LibplanetConsole.Node; using LibplanetConsole.Node.Grpc; @@ -240,7 +241,7 @@ public async ValueTask DisposeAsync() { await _processCancellationTokenSource.CancelAsync(); _processCancellationTokenSource.Dispose(); - await _processTask; + await TaskUtility.TryWait(_processTask); _processTask = Task.CompletedTask; _process = null; From f6c9bfeb2683d7244dad662714858a7b99b0d7b0 Mon Sep 17 00:00:00 2001 From: s2quake Date: Sun, 13 Oct 2024 22:15:47 +0900 Subject: [PATCH 18/18] chore: Readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 465956a4..9e2982bb 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ in different environments. { "$schema": "node-settings-schema.json", "application": { - "endPoint": "localhost:55314", + "port": "55314", "privateKey": "5a3df2ce7fc8b8f7c984f867a34e7d343e974f7b661c83536c0a66685bdbf04a", "storePath": "store", "logPath": "log",