Skip to content

Commit

Permalink
feat: Add swagger and explorer(GQL)
Browse files Browse the repository at this point in the history
  • Loading branch information
s2quake committed Aug 29, 2024
1 parent 8a8b57a commit be76d78
Show file tree
Hide file tree
Showing 19 changed files with 387 additions and 233 deletions.
396 changes: 198 additions & 198 deletions Libplanet.sln

Large diffs are not rendered by default.

38 changes: 38 additions & 0 deletions sdk/node/Libplanet.Node.Executable/Explorer/BlockChainContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Reflection;
using Libplanet.Explorer.Indexing;
using Libplanet.Explorer.Interfaces;
using Libplanet.Net;
using Libplanet.Node.Services;
using Libplanet.Store;

namespace Libplanet.Node.API.Explorer;

internal sealed class BlockChainContext(
IBlockChainService blockChainService, ISwarmService swarmService) : IBlockChainContext
{
public bool Preloaded => false;

public Blockchain.BlockChain BlockChain => blockChainService.BlockChain;

#pragma warning disable S3011 // Reflection should not be used to increase accessibility ...
public IStore Store
{
get
{
var bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance;
var propertyInfo = typeof(BlockChain).GetProperty("Store", bindingFlags) ??
throw new InvalidOperationException("Store property not found.");
if (propertyInfo.GetValue(BlockChain) is IStore store)
{
return store;
}

throw new InvalidOperationException("Store property is not IStore.");
}
}
#pragma warning restore S3011

public Swarm Swarm => swarmService.Swarm;

public IBlockChainIndex Index => throw new NotSupportedException();
}
35 changes: 35 additions & 0 deletions sdk/node/Libplanet.Node.Executable/Explorer/ExplorerExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Libplanet.Explorer;
using Libplanet.Node.Extensions;

namespace Libplanet.Node.API.Explorer;

public static class ExplorerExtensions
{
public static IServiceCollection AddNodeExplorer(
this IServiceCollection services)
{
var serviceProvider = services.BuildServiceProvider();

services.AddSingleton<BlockChainContext>();
services.AddSingleton<ExplorerStartup<BlockChainContext>>();
serviceProvider = services.BuildServiceProvider();
var startUp
= serviceProvider.GetRequiredService<ExplorerStartup<BlockChainContext>>();
startUp.ConfigureServices(services);

return services;
}

public static IApplicationBuilder UseNodeExplorer(this IApplicationBuilder builder)
{
var serviceProvider = builder.ApplicationServices;
var environment = serviceProvider.GetRequiredService<IWebHostEnvironment>();
var startUp = serviceProvider.GetService<ExplorerStartup<BlockChainContext>>();
startUp?.Configure(builder, environment);

return builder;
}

public static bool IsExplorerEnabled(this IHostApplicationBuilder builder)
=> builder.Configuration.IsOptionsEnabled(ExplorerOptions.Position);
}
13 changes: 13 additions & 0 deletions sdk/node/Libplanet.Node.Executable/Explorer/ExplorerOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.ComponentModel;
using Libplanet.Node.Options;

namespace Libplanet.Node.API.Explorer;

[Options(Position)]
public sealed class ExplorerOptions : OptionsBase<ExplorerOptions>, IEnabledOptions
{
public const string Position = "Explorer";

[DefaultValue(true)]
public bool IsEnabled { get; set; } = true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.40.0"/>
<PackageReference Include="Grpc.AspNetCore.Server.Reflection" Version="2.64.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="6.7.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.7.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.7.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.7" />
</ItemGroup>

<ItemGroup>
Expand All @@ -21,6 +25,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\tools\Libplanet.Explorer.Executable\Libplanet.Explorer.Executable.csproj" />
<ProjectReference Include="..\Libplanet.Node.Extensions\Libplanet.Node.Extensions.csproj" />
<ProjectReference Include="..\Libplanet.Node\Libplanet.Node.csproj" />
</ItemGroup>
Expand Down
45 changes: 28 additions & 17 deletions sdk/node/Libplanet.Node.Executable/Program.cs
Original file line number Diff line number Diff line change
@@ -1,48 +1,59 @@
using Libplanet.Node.API.Explorer;
using Libplanet.Node.API.Services;
using Libplanet.Node.Extensions;
using Libplanet.Node.Options.Schema;
using Microsoft.AspNetCore.Server.Kestrel.Core;

SynchronizationContext.SetSynchronizationContext(new());
var builder = WebApplication.CreateBuilder(args);

builder.Logging.AddConsole();

if (builder.Environment.IsDevelopment())
{
builder.WebHost.ConfigureKestrel(options =>
{
// Setup a HTTP/2 endpoint without TLS.
options.ListenLocalhost(5259, o => o.Protocols =
HttpProtocols.Http1AndHttp2);
options.ListenLocalhost(5260, o => o.Protocols =
HttpProtocols.Http2);
});
}

// Additional configuration is required to successfully run gRPC on macOS.
// For instructions on how to configure Kestrel and gRPC clients on macOS,
// visit https://go.microsoft.com/fwlink/?linkid=2099682
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddAuthorization();
builder.Services.AddAuthentication("Bearer").AddJwtBearer();
}

// Add services to the container.
builder.Services.AddGrpc();
builder.Services.AddGrpcReflection();
var libplanetBuilder = builder.Services.AddLibplanetNode(builder.Configuration);
builder.Services.AddLibplanetNode(builder.Configuration);

if (builder.IsExplorerEnabled())
{
builder.Services.AddNodeExplorer();
}

var app = builder.Build();
var handlerMessage = """
Communication with gRPC endpoints must be made through a gRPC client. To learn how to
create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909
""";
var schema = await OptionsSchemaBuilder.GetSchemaAsync(default);
using var app = builder.Build();

// Configure the HTTP request pipeline.
app.MapGrpcService<BlockChainGrpcService>();
app.MapGrpcService<SchemaGrpcService>();
app.MapGrpcService<BlockChainGrpcServiceV1>();
app.MapGrpcService<SchemaGrpcServiceV1>();
app.MapGet("/", () => handlerMessage);
app.MapGet("/schema", () => schema);

if (builder.Environment.IsDevelopment())
{
app.MapGrpcReflectionService().AllowAnonymous();

app.UseSwagger();
app.UseSwaggerUI();
}

app.MapSchemaBuilder("/v1/schema");
app.MapGet("/schema", context => Task.Run(() => context.Response.Redirect("/v1/schema")));

if (builder.IsExplorerEnabled())
{
app.UseNodeExplorer();
}

await app.RunAsync();
2 changes: 1 addition & 1 deletion sdk/node/Libplanet.Node.Executable/Protos/blockchain.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ syntax = "proto3";

option csharp_namespace = "Libplanet.Node.API";

package node;
package node.blockchain.v1;

service BlockChain {
rpc GetGenesisBlock (GetGenesisBlockRequest) returns (GetGenesisBlockReply);
Expand Down
2 changes: 1 addition & 1 deletion sdk/node/Libplanet.Node.Executable/Protos/schema.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ syntax = "proto3";

option csharp_namespace = "Libplanet.Node.API";

package node;
package node.schema.v1;

service Schema {
rpc GetList(GetListRequest) returns (GetListReply);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@

namespace Libplanet.Node.API.Services;

public class BlockChainGrpcService(
IReadChainService blockChain)
: BlockChain.BlockChainBase
public class BlockChainGrpcServiceV1(IReadChainService blockChain) : BlockChain.BlockChainBase
{
private readonly IReadChainService _blockChain = blockChain;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Libplanet.Node.API.Services;

public class SchemaGrpcService : Schema.SchemaBase
internal sealed class SchemaGrpcServiceV1 : Schema.SchemaBase
{
private string[]? _list;
private string? _schema;
Expand Down
15 changes: 15 additions & 0 deletions sdk/node/Libplanet.Node.Executable/appsettings-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1084,6 +1084,17 @@
}
}
},
"Explorer": {
"title": "ExplorerOptions",
"type": "object",
"additionalProperties": false,
"properties": {
"IsEnabled": {
"type": "boolean",
"default": true
}
}
},
"Genesis": {
"title": "GenesisOptions",
"type": "object",
Expand Down Expand Up @@ -1218,6 +1229,10 @@
{
"type": "object",
"properties": {
"Explorer": {
"description": "Type 'ExplorerOptions' does not have a description.",
"$ref": "#/definitions/Explorer"
},
"Genesis": {
"description": "Options for the genesis block.",
"$ref": "#/definitions/Genesis"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@
},
"Kestrel": {
"EndpointDefaults": {
"Protocols": "Http1AndHttp2"
"Protocols": "Http2"
}
},
"Swarm": {
"IsEnabled": true
},
"Validator": {
"IsEnabled": true
},
"Explorer": {
"IsEnabled": true
}
}
40 changes: 32 additions & 8 deletions sdk/node/Libplanet.Node.Extensions/LibplanetServicesExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
using Libplanet.Blockchain;
using Libplanet.Node.Extensions.NodeBuilder;
using Libplanet.Node.Options;
using Libplanet.Node.Options.Schema;
using Libplanet.Node.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
Expand Down Expand Up @@ -43,27 +44,50 @@ public static ILibplanetNodeBuilder AddLibplanetNode(
services.AddSingleton<IReadChainService, ReadChainService>();
services.AddSingleton<TransactionService>();

var serviceProvider = services.BuildServiceProvider();
var soloOptions = serviceProvider.GetRequiredService<IOptions<SoloOptions>>();
var swarmOptions = serviceProvider.GetRequiredService<IOptions<SwarmOptions>>();
var validatorOptions = serviceProvider.GetRequiredService<IOptions<ValidatorOptions>>();
var nodeBuilder = new LibplanetNodeBuilder(services);

if (soloOptions.Value.IsEnabled)
if (configuration.IsOptionsEnabled(SoloOptions.Position))
{
nodeBuilder.WithSolo();
}

if (swarmOptions.Value.IsEnabled)
if (configuration.IsOptionsEnabled(SwarmOptions.Position))
{
nodeBuilder.WithSwarm();
}

if (validatorOptions.Value.IsEnabled)
if (configuration.IsOptionsEnabled(ValidatorOptions.Position))
{
nodeBuilder.WithValidator();
}

return nodeBuilder;
}

public static IApplicationBuilder MapSchemaBuilder(this IApplicationBuilder app, string pattern)
{
app.UseRouting();
app.UseEndpoints(endPoint =>
{
string? schema = null;
endPoint.MapGet(pattern, async () =>
{
schema ??= await OptionsSchemaBuilder.GetSchemaAsync(default);
return schema;
});
});

return app;
}

public static bool IsOptionsEnabled(
this IConfiguration configuration, string name)
=> configuration.GetValue<bool>($"{name}:IsEnabled");

public static bool IsOptionsEnabled(
this IConfiguration configuration, string name, string propertyName)
{
var key = $"{name}:{propertyName}";
return configuration.GetValue<bool>(key);
}
}
6 changes: 6 additions & 0 deletions sdk/node/Libplanet.Node/Options/IEnabledOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Libplanet.Node.Options;

public interface IEnabledOptions
{
bool IsEnabled { get; }
}
2 changes: 1 addition & 1 deletion sdk/node/Libplanet.Node/Options/SoloOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
namespace Libplanet.Node.Options;

[Options(Position)]
public class SoloOptions : OptionsBase<SoloOptions>
public class SoloOptions : OptionsBase<SoloOptions>, IEnabledOptions
{
public const string Position = "Solo";

Expand Down
2 changes: 1 addition & 1 deletion sdk/node/Libplanet.Node/Options/SwarmOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
namespace Libplanet.Node.Options;

[Options(Position)]
public sealed class SwarmOptions : OptionsBase<SwarmOptions>
public sealed class SwarmOptions : OptionsBase<SwarmOptions>, IEnabledOptions
{
public const string Position = "Swarm";

Expand Down
2 changes: 1 addition & 1 deletion sdk/node/Libplanet.Node/Options/ValidatorOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
namespace Libplanet.Node.Options;

[Options(Position)]
public sealed class ValidatorOptions : OptionsBase<ValidatorOptions>
public sealed class ValidatorOptions : OptionsBase<ValidatorOptions>, IEnabledOptions
{
public const string Position = "Validator";

Expand Down
4 changes: 4 additions & 0 deletions sdk/node/Libplanet.Node/Services/ISwarmService.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
using Libplanet.Net;

namespace Libplanet.Node.Services;

public interface ISwarmService
{
public event EventHandler? Started;

public event EventHandler? Stopped;

Swarm Swarm { get; }
}
2 changes: 2 additions & 0 deletions sdk/node/Libplanet.Node/Services/SwarmService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ internal sealed class SwarmService(

public bool IsRunning => _swarm is not null;

public Swarm Swarm => _swarm ?? throw new InvalidOperationException("Node is not running.");

public async Task StartAsync(CancellationToken cancellationToken)
{
if (_swarm is not null)
Expand Down

0 comments on commit be76d78

Please sign in to comment.