diff --git a/Directory.Build.props b/Directory.Build.props index 8f4050aa..94ae4d5b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ 2024 Mithril Man. - 0.3.0 + 0.4.0 false true diff --git a/docs/docs-placeholder.csproj b/docs/docs-placeholder.csproj index 75106c14..96aa6838 100644 --- a/docs/docs-placeholder.csproj +++ b/docs/docs-placeholder.csproj @@ -1,6 +1,6 @@ - net5.0 + net8.0 false false diff --git a/docs/example-projects/mithril-shards-example.md b/docs/example-projects/mithril-shards-example.md index 45125b99..97fa0837 100644 --- a/docs/example-projects/mithril-shards-example.md +++ b/docs/example-projects/mithril-shards-example.md @@ -87,7 +87,7 @@ public class ExampleClientPeerBinding ExampleClientPeerBinding class has an EndPoint property that represents the endpoint (IP:Address) of a remote node we'd like to connect to and it's decorated with attributes that are used to validate the configuration during the initialization of the forge. -Validation of the settings make use of [System.ComponentModel.DataAnnotations](https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations?view=net-5.0){:target="_blank"} and [RequiredAttribute](https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.requiredattribute?view=net-5.0){:target="_blank"} used in the example is one of those attribute that belongs to standard set. +Validation of the settings make use of [System.ComponentModel.DataAnnotations](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations?view=net-8.0){:target="_blank"} and [RequiredAttribute](https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.requiredattribute?view=net-8.0){:target="_blank"} used in the example is one of those attribute that belongs to standard set. A different story is [IPEndPointValidator]: ```c# diff --git a/docs/index.md b/docs/index.md index 81f90a36..30727448 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,7 +6,7 @@ description: Mithril Shards home page ## Goal -Mithril Shards goal is to implement a .NET 6 extensible P2P network & distributed services library from scratch with focus on architecture and performance. +Mithril Shards goal is to implement a .NET 8 extensible P2P network & distributed services library from scratch with focus on architecture and performance. Allows you to define custom network serialization protocol, easily handle payload messages and interact with the software leveraging any available features (named Shards) like Web API endpoints, cross platform Blazor UI, and a lot of other exciting stuffs that community can implement and release to the public too! @@ -18,7 +18,7 @@ The project is very ambitious and it's currently developed just by me as a pet p A random list of available tech used within Mithril Shards. -- [.Net 6](https://dotnet.microsoft.com/download/dotnet/6.0){:target="_blank"} - ... for everything. +- [.Net 8](https://dotnet.microsoft.com/download/dotnet/8.0){:target="_blank"} - ... for everything. - [Bedrock Framework](https://github.com/davidfowl/BedrockFramework/){:target="_blank"} - TCP/IP default connectivity implementation. - [Swashbuckle](https://github.com/domaindrivendev/Swashbuckle.AspNetCore){:target="_blank"} - to handle Web API in a configurable, multi-area environment and have a playground to test APIs with swagger. - [Serilog](https://github.com/serilog/serilog-aspnetcore){:target="_blank"} - default logging implementation. diff --git a/docs/mithril-shards/forge-builder.md b/docs/mithril-shards/forge-builder.md index 0762d58f..f56004b0 100644 --- a/docs/mithril-shards/forge-builder.md +++ b/docs/mithril-shards/forge-builder.md @@ -7,7 +7,7 @@ description: Mithril Shards implementation, ForgeBuilder class ForgeBuilder class represents the entry point of a Mithril Shards application, it allows to add a shard by calling the generic `AddShard` method, with different overloads that accept an optional strongly typed shard setting file with an optional setting file validator. -By using `ConfigureLogging` it's possible to configure logging, it's basically a wrapper on the inner [HostBuilder ConfigureLogging](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.webhostbuilder.configurelogging?view=aspnetcore-1.1&viewFallbackFrom=aspnetcore-5.0){:target="_blank"} method, you could use it to have a finer control over logging configuration and available providers, but the easier way to log is by using the available [SerilogShard] that uses Serilog to configure the logging and relies on a configurable setting file where you can specify which sink to use. +By using `ConfigureLogging` it's possible to configure logging, it's basically a wrapper on the inner [HostBuilder ConfigureLogging](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.webhostbuilder.configurelogging?view=aspnetcore-1.1&viewFallbackFrom=aspnetcore-8.0){:target="_blank"} method, you could use it to have a finer control over logging configuration and available providers, but the easier way to log is by using the available [SerilogShard] that uses Serilog to configure the logging and relies on a configurable setting file where you can specify which sink to use. You can find more details on its specific documentation page and an example of its usage in the [example project] After declaring an instance we have to specify which implementation of Forge we want to use. diff --git a/docs/mithril-shards/index.md b/docs/mithril-shards/index.md index 91b0b66b..fe2cc964 100644 --- a/docs/mithril-shards/index.md +++ b/docs/mithril-shards/index.md @@ -12,7 +12,7 @@ Pretending to be into a Tolkien universe, I thought of defining features as *sha To find analogies with .net naming conventions: -- **Forge** (to be more precise, **ForgeBuilder**) is a [HostBuilder](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.hostbuilder?view=dotnet-plat-ext-5.0){:target="_blank"} on steroids (actually it encapsulate a HostBuilder instance). +- **Forge** (to be more precise, **ForgeBuilder**) is a [HostBuilder](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.hostbuilder?view=net-8.0){:target="_blank"} on steroids (actually it encapsulate a HostBuilder instance). - **Shard** is an application part that gets merged into the hostbuilder, using dependency injection, extending its functionality - **Artifact** is just an allegoric view of the result of `forgeBuilderInstance.RunConsoleAsync()`. diff --git a/docs/shards/bitcoin/index.md b/docs/shards/bitcoin/index.md index 6b8f7406..d76bd058 100644 --- a/docs/shards/bitcoin/index.md +++ b/docs/shards/bitcoin/index.md @@ -30,7 +30,7 @@ Also if you have question or want to discuss about technical details, you can us ## Project Overview **Bitcoin Mithril Shard** has been built on top of **Mithril Shards** framework. -Mithril Shards goal is to be a framework and toolkit to build *modular and distributed/P2P applications using .Net 6* stack, focusing both on good design, good practices and performance. +Mithril Shards goal is to be a framework and toolkit to build *modular and distributed/P2P applications using .Net 8* stack, focusing both on good design, good practices and performance. Core functionalities can be glued togheter to compose the needed application, ranging from a P2P network layer, Web Api layer, Diagnostic tools, cross platform UI based on blazor, distributed eventing using SignalR, MQ brokers like RabbitMq or any kind of other useful libraries. diff --git a/docs/shards/bitcoin/network.md b/docs/shards/bitcoin/network.md index c844e77e..0f0cf768 100644 --- a/docs/shards/bitcoin/network.md +++ b/docs/shards/bitcoin/network.md @@ -23,7 +23,7 @@ Before being able to handshake, whenever a connection has been established betwe ## Peer Context -Default Mithril Shards implementation uses `PeerContext` class to store, among other things, information like peer unique identification, direction (inbound/outbound) remote and local endpoints, user agent identification, negotiated protocol version and other attachable properties leveraging the .Net [IFeatureCollection](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.features.ifeaturecollection?view=aspnetcore-5.0){:target="_blank"} interface. +Default Mithril Shards implementation uses `PeerContext` class to store, among other things, information like peer unique identification, direction (inbound/outbound) remote and local endpoints, user agent identification, negotiated protocol version and other attachable properties leveraging the .Net [IFeatureCollection](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.features.ifeaturecollection?view=aspnetcore-8.0){:target="_blank"} interface. Bitcoin needs some additional information and some of the properties that are ubiquitous needed among all optional Bitcoin features (shards) like wallet, APIs, indexer, etc... have been defined directly into `BitcoinPeerContext` that extends the default `PeerContext`. Some of the additional properties are Permissions (that may change the FN behavior based on its set) and TimeOffset, that's an important aspect for the consensus logic. diff --git a/docs/shards/web-api/creating-a-controller.md b/docs/shards/web-api/creating-a-controller.md index 43e7c00a..bb1ea1a3 100644 --- a/docs/shards/web-api/creating-a-controller.md +++ b/docs/shards/web-api/creating-a-controller.md @@ -36,7 +36,7 @@ public class ExampleController : MithrilControllerBase ### Define the area -Line 1 describes, using [AreaAttribute](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.areaattribute?view=aspnetcore-5.0){:target="_blank"}, the area we want this controller to be included. +Line 1 describes, using [AreaAttribute](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.areaattribute?view=aspnetcore-8.0){:target="_blank"}, the area we want this controller to be included. WebApiArea is an helper class that just contains a bounch of constant string of known areas: "api" and "dev". @@ -92,7 +92,7 @@ Lines 13-18 declare and implement an action. In this case, the action is declared as `HttpGet`, this mean that it will only respond to GET requests. If you try to access that action with others HTTP verbs, it will return a status error `405 Method Not Allowed` `[ProducesResponseType(StatusCodes.Status200OK)]` declare known action status that can be returned by the action. -It's just an helpful attribute useful to produce a better documentation on swagger interface and document definition but the action itself may even generate different statuses. This documentation however don't cover canonical Web API implementation, so [refer to .Net documentation](https://docs.microsoft.com/it-it/aspnet/core/web-api/advanced/conventions?view=aspnetcore-5.0){:target="_blank"}. to read more about it. +It's just an helpful attribute useful to produce a better documentation on swagger interface and document definition but the action itself may even generate different statuses. This documentation however don't cover canonical Web API implementation, so [refer to .Net documentation](https://learn.microsoft.com/en-us/aspnet/core/web-api/advanced/conventions?view=aspnetcore-8.0){:target="_blank"}. to read more about it. To return an action result, the method `Ok` is invoked, passing the payload (that will be serialized in JSON) as a response. @@ -132,12 +132,12 @@ public IActionResult Connect(PeerManagementConnectRequest request) In this example, this action will return 404 (not found) if the member variables _requiredConnection isn't set, or 400 (bad request) if the input peer isn't formatted properly as a valid endpoint. If everything goes fine it will instead return 200 (ok). !!! tip - In case of action problems, instead of calling BadRequest or Problem method extensions, use `ValidationProblem`, it uses a ValidationProblemDetails response that's consistent with automatic validation error responses, [as stated here](https://docs.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-5.0#default-badrequest-response){:target="_blank"}. + In case of action problems, instead of calling BadRequest or Problem method extensions, use `ValidationProblem`, it uses a ValidationProblemDetails response that's consistent with automatic validation error responses, [as stated here](https://docs.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-8.0#default-badrequest-response){:target="_blank"}. In the example above, NotFound could be replaced with ValidationProblem too for a consistent behavior. ### Producing documentation for Swagger UI -In order to produce proper documentation to be shown on Swagger UI, [XML comments within C#](https://docs.microsoft.com/it-it/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-5.0&tabs=visual-studio#xml-comments){:target="_blank"} source can be used but the build process has to generate a documentation file. +In order to produce proper documentation to be shown on Swagger UI, [XML comments within C#](https://docs.microsoft.com/it-it/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-8.0&tabs=visual-studio#xml-comments){:target="_blank"} source can be used but the build process has to generate a documentation file. The easier way is to edit your project file adding this snippet: diff --git a/docs/shards/web-api/index.md b/docs/shards/web-api/index.md index cae3e42d..4f2eb62d 100644 --- a/docs/shards/web-api/index.md +++ b/docs/shards/web-api/index.md @@ -6,7 +6,7 @@ description: Mithril Shards, WebApiShard Overview WebApiShard is an important shard that allows to expose Web API endpoints based on [OpeAPI specifications](https://swagger.io/specification/){:target="_blank"}. -[Swashbuckle](https://github.com/domaindrivendev/Swashbuckle.AspNetCore){:target="_blank"} is used under the hood and you can find more technical information about OpenAPI, REST APIs and Swagger concepts on [microsoft documentation](https://docs.microsoft.com/it-it/aspnet/core/tutorials/web-api-help-pages-using-swagger?view=aspnetcore-5.0){:target="_blank"}. +[Swashbuckle](https://github.com/domaindrivendev/Swashbuckle.AspNetCore){:target="_blank"} is used under the hood and you can find more technical information about OpenAPI, REST APIs and Swagger concepts on [microsoft documentation](https://learn.microsoft.com/en-us/aspnet/core/tutorials/web-api-help-pages-using-swagger?view=aspnetcore-8.0){:target="_blank"}. WebApiShard comes with a [WebApiSettings] class that holds settings to configure the service. @@ -16,7 +16,7 @@ To add the shard to the forge, the `IForgeBuilder` extension `UseApi` has to be public static IForgeBuilder UseApi(this IForgeBuilder forgeBuilder, Action? options = null) ``` -WebApiShard implements the Web API controllers using the standard aspnet [ControllerBase](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.controllerbase?view=aspnetcore-5.0){:target="_blank"} class but decorates it with a set of default attributes needed to expose these controllers in the right context. +WebApiShard implements the Web API controllers using the standard aspnet [ControllerBase](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.controllerbase?view=aspnetcore-8.0){:target="_blank"} class but decorates it with a set of default attributes needed to expose these controllers in the right context. To create a proper WebApiShard controller, an abstract base class `MithrilControllerBase` exists that applies already the required attributes. ```c# @@ -55,7 +55,7 @@ public abstract class WebApiArea !!! note WebApiShard controllers have to belong to a specific area. More information in [Creating a Controller] section. -`DisableByEndPointActionFilterAttribute` class, that's a registered [ActionFilterAttribute](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.filters.actionfilterattribute?view=aspnetcore-5.0){:target="_blank"}, is responsible to enforce proper checks against executing an action on an unspecified, unknown or disabled area. +`DisableByEndPointActionFilterAttribute` class, that's a registered [ActionFilterAttribute](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.filters.actionfilterattribute?view=aspnetcore-8.0){:target="_blank"}, is responsible to enforce proper checks against executing an action on an unspecified, unknown or disabled area. !!! warning The current implementation may be subject to changes to implement the authentication and authorization layer. \ No newline at end of file diff --git a/docs/shards/web-api/using-webapishard.md b/docs/shards/web-api/using-webapishard.md index dfb1a927..1d0b4052 100644 --- a/docs/shards/web-api/using-webapishard.md +++ b/docs/shards/web-api/using-webapishard.md @@ -19,7 +19,7 @@ public static IForgeBuilder UseApi(this IForgeBuilder forgeBuilder, Action(); + INetworkProtocolMessageSerializer protocol = serviceProviderScope.ServiceProvider.GetRequiredService(); IPeerContext peerContext = peerContextFactory.CreateOutgoingPeerContext(connection.ConnectionId, connection.LocalEndPoint!, diff --git a/src/MithrilShards.Network.Bedrock/MithrilForgeServerConnectionHandler.cs b/src/MithrilShards.Network.Bedrock/MithrilForgeServerConnectionHandler.cs index e6c62977..42d4eda9 100644 --- a/src/MithrilShards.Network.Bedrock/MithrilForgeServerConnectionHandler.cs +++ b/src/MithrilShards.Network.Bedrock/MithrilForgeServerConnectionHandler.cs @@ -17,43 +17,30 @@ namespace MithrilShards.Network.Bedrock; -public class MithrilForgeServerConnectionHandler : ConnectionHandler +public class MithrilForgeServerConnectionHandler( + ILogger logger, + IServiceProvider serviceProvider, + IEventBus eventBus, + IEnumerable serverPeerConnectionGuards, + INetworkMessageProcessorFactory networkMessageProcessorFactory, + IPeerContextFactory peerContextFactory) : ConnectionHandler { - private readonly ILogger _logger; - private readonly IServiceProvider _serviceProvider; - private readonly IEventBus _eventBus; - private readonly IEnumerable _serverPeerConnectionGuards; - private readonly INetworkMessageProcessorFactory _networkMessageProcessorFactory; - private readonly IPeerContextFactory _peerContextFactory; - - public MithrilForgeServerConnectionHandler(ILogger logger, - IServiceProvider serviceProvider, - IEventBus eventBus, - IEnumerable serverPeerConnectionGuards, - INetworkMessageProcessorFactory networkMessageProcessorFactory, - IPeerContextFactory peerContextFactory) - { - _logger = logger; - _serviceProvider = serviceProvider; - _eventBus = eventBus; - _serverPeerConnectionGuards = serverPeerConnectionGuards; - _networkMessageProcessorFactory = networkMessageProcessorFactory; - _peerContextFactory = peerContextFactory; - } public override async Task OnConnectedAsync(ConnectionContext connection) { ArgumentNullException.ThrowIfNull(connection); - using var _ = _logger.BeginScope("Peer {PeerId} connected to server {ServerEndpoint}", connection.ConnectionId, connection.LocalEndPoint); + using var serviceProviderScope = serviceProvider.CreateScope(); + using var _ = logger.BeginScope("Peer {PeerId} connected to server {ServerEndpoint}", connection.ConnectionId, connection.LocalEndPoint); ProtocolReader reader = connection.CreateReader(); - INetworkProtocolMessageSerializer protocol = _serviceProvider.GetRequiredService(); + var protocolSerializer = serviceProviderScope.ServiceProvider.GetRequiredService(); - IPeerContext peerContext = _peerContextFactory.CreateIncomingPeerContext(connection.ConnectionId, - connection.LocalEndPoint!.AsIPEndPoint().EnsureIPv6(), - connection.RemoteEndPoint!.AsIPEndPoint().EnsureIPv6(), - new NetworkMessageWriter(protocol, connection.CreateWriter())); + IPeerContext peerContext = peerContextFactory.CreateIncomingPeerContext( + connection.ConnectionId, + connection.LocalEndPoint!.AsIPEndPoint().EnsureIPv6(), + connection.RemoteEndPoint!.AsIPEndPoint().EnsureIPv6(), + new NetworkMessageWriter(protocolSerializer, connection.CreateWriter())); // will dispose peerContext when out of scope, see https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-disposeasync#using-async-disposable await using var asyncDisposablePeerContext = peerContext.ConfigureAwait(false); @@ -64,20 +51,20 @@ public override async Task OnConnectedAsync(ConnectionContext connection) }); connection.Features.Set(peerContext); - protocol.SetPeerContext(peerContext); + protocolSerializer.SetPeerContext(peerContext); if (await EnsurePeerCanConnectAsync(connection, peerContext).ConfigureAwait(false)) { - await _eventBus.PublishAsync(new PeerConnected(peerContext)).ConfigureAwait(false); + await eventBus.PublishAsync(new PeerConnected(peerContext)).ConfigureAwait(false); - await _networkMessageProcessorFactory.StartProcessorsAsync(peerContext).ConfigureAwait(false); + await networkMessageProcessorFactory.StartProcessorsAsync(peerContext).ConfigureAwait(false); while (true) { try { - ProtocolReadResult result = await reader.ReadAsync(protocol).ConfigureAwait(false); + ProtocolReadResult result = await reader.ReadAsync(protocolSerializer).ConfigureAwait(false); if (result.IsCompleted) { @@ -88,7 +75,7 @@ public override async Task OnConnectedAsync(ConnectionContext connection) } catch (Exception ex) { - _logger.LogDebug(ex, "Unexpected connection terminated because of {DisconnectionReason}.", ex.Message); + logger.LogDebug(ex, "Unexpected connection terminated because of {DisconnectionReason}.", ex.Message); break; } finally @@ -107,10 +94,10 @@ public override async Task OnConnectedAsync(ConnectionContext connection) /// When criteria is met returns true, to allow connection. private async ValueTask EnsurePeerCanConnectAsync(ConnectionContext connection, IPeerContext peerContext) { - if (_serverPeerConnectionGuards == null) return false; + if (serverPeerConnectionGuards == null) return false; ServerPeerConnectionGuardResult? result = ( - from guard in _serverPeerConnectionGuards + from guard in serverPeerConnectionGuards let guardResult = guard.Check(peerContext) where guardResult.IsDenied select guardResult @@ -122,9 +109,9 @@ select guardResult if (result.IsDenied) { - _logger.LogDebug("Connection from client '{ConnectingPeerEndPoint}' was rejected because of {ClientDisconnectedReason} and will be closed.", connection.RemoteEndPoint, result.DenyReason); + logger.LogDebug("Connection from client '{ConnectingPeerEndPoint}' was rejected because of {ClientDisconnectedReason} and will be closed.", connection.RemoteEndPoint, result.DenyReason); connection.Abort(new ConnectionAbortedException(result.DenyReason)); - await _eventBus.PublishAsync(new PeerConnectionRejected(peerContext, result.DenyReason)).ConfigureAwait(false); + await eventBus.PublishAsync(new PeerConnectionRejected(peerContext, result.DenyReason)).ConfigureAwait(false); return false; } @@ -133,12 +120,12 @@ select guardResult private async Task ProcessMessageAsync(INetworkMessage message, IPeerContext peerContext, CancellationToken cancellation) { - using var _ = _logger.BeginScope("Processing message '{Command}'", message.Command); + using var _ = logger.BeginScope("Processing message '{Command}'", message.Command); if (message is not UnknownMessage) { - await _networkMessageProcessorFactory.ProcessMessageAsync(message, peerContext, cancellation).ConfigureAwait(false); - await _eventBus.PublishAsync(new PeerMessageReceived(peerContext, message), cancellation).ConfigureAwait(false); + await networkMessageProcessorFactory.ProcessMessageAsync(message, peerContext, cancellation).ConfigureAwait(false); + await eventBus.PublishAsync(new PeerMessageReceived(peerContext, message), cancellation).ConfigureAwait(false); } } } \ No newline at end of file