Skip to content

Commit

Permalink
Merge pull request planetarium#2230 from planetarium/release/40
Browse files Browse the repository at this point in the history
Backmerge to development
  • Loading branch information
U-lis authored Sep 1, 2023
2 parents 7cea74b + 7967f5d commit f3f91fd
Show file tree
Hide file tree
Showing 11 changed files with 438 additions and 10 deletions.
7 changes: 5 additions & 2 deletions NineChronicles.Headless.Executable/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ public async Task Run(
: (GraphQLNodeServiceProperties.MagicOnionHttpOptions?)null,
};

var graphQLService = new GraphQLService(graphQLNodeServiceProperties);
var graphQLService = new GraphQLService(graphQLNodeServiceProperties, standaloneContext, configuration);
hostBuilder = graphQLService.Configure(hostBuilder);
}

Expand Down Expand Up @@ -511,9 +511,12 @@ IActionLoader MakeSingleActionLoader()
context,
new ConcurrentDictionary<string, Sentry.ITransaction>()
);

hostBuilder.UseNineChroniclesRPC(
rpcProperties,
publisher
publisher,
standaloneContext,
configuration
);
}

Expand Down
6 changes: 6 additions & 0 deletions NineChronicles.Headless.Executable/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,5 +122,11 @@
"ContentType": "application/json",
"StatusCode": 403
}
},
"MultiAccountManaging": {
"EnableManaging": false,
"ManagementTimeMinutes": 10,
"TxIntervalMinutes": 10,
"ThresholdCount": 50
}
}
4 changes: 3 additions & 1 deletion NineChronicles.Headless.Tests/GraphQLStartupTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using static NineChronicles.Headless.Tests.GraphQLTestUtils;
using Xunit;

namespace NineChronicles.Headless.Tests
Expand All @@ -14,7 +15,8 @@ public class GraphQLStartupTest
public GraphQLStartupTest()
{
_configuration = new ConfigurationBuilder().AddInMemoryCollection().Build();
_startup = new GraphQLService.GraphQLStartup(_configuration);
var standaloneContext = CreateStandaloneContext();
_startup = new GraphQLService.GraphQLStartup(_configuration, standaloneContext);
}

[Theory]
Expand Down
27 changes: 24 additions & 3 deletions NineChronicles.Headless/GraphQLService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
using GraphQL.Utilities;
using Grpc.Core;
using Grpc.Net.Client;
using Libplanet.Crypto;
using Libplanet.Explorer.Schemas;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using NineChronicles.Headless.GraphTypes;
using NineChronicles.Headless.Middleware;
using NineChronicles.Headless.Properties;
Expand All @@ -33,11 +35,15 @@ public class GraphQLService

public const string MagicOnionTargetKey = "magicOnionTarget";

private StandaloneContext StandaloneContext { get; }
private GraphQLNodeServiceProperties GraphQlNodeServiceProperties { get; }
private IConfiguration Configuration { get; }

public GraphQLService(GraphQLNodeServiceProperties properties)
public GraphQLService(GraphQLNodeServiceProperties properties, StandaloneContext standaloneContext, IConfiguration configuration)
{
GraphQlNodeServiceProperties = properties;
StandaloneContext = standaloneContext;
Configuration = configuration;
}

public IHostBuilder Configure(IHostBuilder hostBuilder)
Expand All @@ -47,7 +53,7 @@ public IHostBuilder Configure(IHostBuilder hostBuilder)

return hostBuilder.ConfigureWebHostDefaults(builder =>
{
builder.UseStartup<GraphQLStartup>();
builder.UseStartup(x => new GraphQLStartup(x.Configuration, StandaloneContext));
builder.ConfigureAppConfiguration(
(context, builder) =>
{
Expand Down Expand Up @@ -86,12 +92,14 @@ public IHostBuilder Configure(IHostBuilder hostBuilder)

internal class GraphQLStartup
{
public GraphQLStartup(IConfiguration configuration)
public GraphQLStartup(IConfiguration configuration, StandaloneContext standaloneContext)
{
Configuration = configuration;
StandaloneContext = standaloneContext;
}

public IConfiguration Configuration { get; }
public StandaloneContext StandaloneContext;

public void ConfigureServices(IServiceCollection services)
{
Expand All @@ -105,6 +113,11 @@ public void ConfigureServices(IServiceCollection services)
services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
}

if (Convert.ToBoolean(Configuration.GetSection("MultiAccountManaging")["EnableManaging"]))
{
services.Configure<MultiAccountManagerProperties>(Configuration.GetSection("MultiAccountManaging"));
}

if (!(Configuration[NoCorsKey] is null))
{
services.AddCors(
Expand Down Expand Up @@ -154,6 +167,14 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
}

// Capture requests
if (Convert.ToBoolean(Configuration.GetSection("MultiAccountManaging")["EnableManaging"]))
{
Dictionary<string, HashSet<Address>> ipSignerList = new();
app.UseMiddleware<HttpMultiAccountManagementMiddleware>(
StandaloneContext,
ipSignerList);
}

app.UseMiddleware<HttpCaptureMiddleware>();

app.UseMiddleware<LocalAuthenticationMiddleware>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ public EquipmentType()
Field<NonNullGraphType<GuidGraphType>>(nameof(Equipment.ItemId));
Field<NonNullGraphType<IntGraphType>>(nameof(Equipment.level),
resolve: context => context.Source.level);
Field<NonNullGraphType<IntGraphType>>(nameof(Equipment.Exp));
Field<NonNullGraphType<IntGraphType>>(nameof(Equipment.Exp),
resolve: context => context.Source.Exp);
Field<ListGraphType<SkillType>>(nameof(Equipment.Skills));
Field<ListGraphType<SkillType>>(nameof(Equipment.BuffSkills));
Field<NonNullGraphType<StatsMapType>>(nameof(Equipment.StatsMap));
Expand Down
22 changes: 21 additions & 1 deletion NineChronicles.Headless/HostBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using NineChronicles.Headless.Properties;
using System.Net;
using Lib9c.Formatters;
using Libplanet.Action;
using Libplanet.Crypto;
using Libplanet.Headless.Hosting;
using MessagePack;
using MessagePack.Resolvers;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Configuration;
using Nekoyume.Action;
using NineChronicles.Headless.Middleware;
using NineChronicles.Headless.Services;
Expand Down Expand Up @@ -68,7 +72,9 @@ StandaloneContext context
public static IHostBuilder UseNineChroniclesRPC(
this IHostBuilder builder,
RpcNodeServiceProperties properties,
ActionEvaluationPublisher actionEvaluationPublisher
ActionEvaluationPublisher actionEvaluationPublisher,
StandaloneContext standaloneContext,
IConfiguration configuration
)
{
var context = new RpcContext
Expand All @@ -79,10 +85,24 @@ ActionEvaluationPublisher actionEvaluationPublisher
return builder
.ConfigureServices(services =>
{
if (Convert.ToBoolean(configuration.GetSection("MultiAccountManaging")["EnableManaging"]))
{
services.Configure<MultiAccountManagerProperties>(configuration.GetSection("MultiAccountManaging"));
}

services.AddSingleton(_ => context);
services.AddGrpc(options =>
{
options.MaxReceiveMessageSize = null;
if (Convert.ToBoolean(configuration.GetSection("MultiAccountManaging")["EnableManaging"]))
{
Dictionary<string, HashSet<Address>> ipSignerList = new();
options.Interceptors.Add<GrpcMultiAccountManagementMiddleware>(
standaloneContext,
ipSignerList,
actionEvaluationPublisher);
}

options.Interceptors.Add<GrpcCaptureMiddleware>(actionEvaluationPublisher);
});
services.AddMagicOnion();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace NineChronicles.Headless.Middleware
public class GrpcCaptureMiddleware : Interceptor
{
private readonly ILogger _logger;
private ActionEvaluationPublisher _actionEvaluationPublisher;
private readonly ActionEvaluationPublisher _actionEvaluationPublisher;

public GrpcCaptureMiddleware(ActionEvaluationPublisher actionEvaluationPublisher)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
using System;
using System.Collections.Generic;
using Grpc.Core;
using Grpc.Core.Interceptors;
using System.Threading.Tasks;
using Libplanet.Crypto;
using Libplanet.Types.Tx;
using Microsoft.Extensions.Options;
using NineChronicles.Headless.Properties;
using Serilog;

namespace NineChronicles.Headless.Middleware
{
public class GrpcMultiAccountManagementMiddleware : Interceptor
{
private static readonly Dictionary<Address, DateTimeOffset> MultiAccountTxIntervalTracker = new();
private static readonly Dictionary<Address, DateTimeOffset> MultiAccountManagementList = new();
private readonly ILogger _logger;
private StandaloneContext _standaloneContext;
private readonly Dictionary<string, HashSet<Address>> _ipSignerList;
private readonly ActionEvaluationPublisher _actionEvaluationPublisher;
private readonly IOptions<MultiAccountManagerProperties> _options;

public GrpcMultiAccountManagementMiddleware(
StandaloneContext standaloneContext,
Dictionary<string, HashSet<Address>> ipSignerList,
ActionEvaluationPublisher actionEvaluationPublisher,
IOptions<MultiAccountManagerProperties> options)
{
_logger = Log.Logger.ForContext<GrpcMultiAccountManagementMiddleware>();
_standaloneContext = standaloneContext;
_ipSignerList = ipSignerList;
_actionEvaluationPublisher = actionEvaluationPublisher;
_options = options;
}

private static void ManageMultiAccount(Address agent)
{
MultiAccountManagementList.Add(agent, DateTimeOffset.Now);
}

private static void RestoreMultiAccount(Address agent)
{
MultiAccountManagementList.Remove(agent);
}

public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request, ServerCallContext context, UnaryServerMethod<TRequest, TResponse> continuation)
{
if (context.Method is "/IBlockChainService/AddClient" && request is byte[] addClientAddressBytes)
{
var agent = new Address(addClientAddressBytes);
var httpContext = context.GetHttpContext();

if (_options.Value.EnableManaging)
{
if (!_ipSignerList.ContainsKey(httpContext.Connection.RemoteIpAddress!.ToString()))
{
_logger.Information(
"[GRPC-MULTI-ACCOUNT-MANAGER] Creating a new list for IP: {IP}",
httpContext.Connection.RemoteIpAddress!.ToString());
_ipSignerList[httpContext.Connection.RemoteIpAddress!.ToString()] = new HashSet<Address>();
}
else
{
_logger.Information(
"[GRPC-MULTI-ACCOUNT-MANAGER] List already created for IP: {IP} Count: {Count}",
httpContext.Connection.RemoteIpAddress!.ToString(),
_ipSignerList[httpContext.Connection.RemoteIpAddress!.ToString()].Count);
}

_ipSignerList[httpContext.Connection.RemoteIpAddress!.ToString()].Add(agent);
}
}

if (context.Method is "/IBlockChainService/GetNextTxNonce" && request is byte[] getNextTxNonceAddressBytes)
{
var agent = new Address(getNextTxNonceAddressBytes);
var httpContext = context.GetHttpContext();

if (_options.Value.EnableManaging)
{
if (!_ipSignerList.ContainsKey(httpContext.Connection.RemoteIpAddress!.ToString()))
{
_logger.Information(
"[GRPC-MULTI-ACCOUNT-MANAGER] Creating a new list for IP: {IP}",
httpContext.Connection.RemoteIpAddress!.ToString());
_ipSignerList[httpContext.Connection.RemoteIpAddress!.ToString()] = new HashSet<Address>();
}
else
{
_logger.Information(
"[GRPC-MULTI-ACCOUNT-MANAGER] List already created for IP: {IP} Count: {Count}",
httpContext.Connection.RemoteIpAddress!.ToString(),
_ipSignerList[httpContext.Connection.RemoteIpAddress!.ToString()].Count);
}

_ipSignerList[httpContext.Connection.RemoteIpAddress!.ToString()].Add(agent);
}
}

if (context.Method is "/IBlockChainService/PutTransaction" && request is byte[] txBytes)
{
Transaction tx =
Transaction.Deserialize(txBytes);
var httpContext = context.GetHttpContext();

if (_options.Value.EnableManaging)
{
var agent = tx.Signer;
if (_ipSignerList[httpContext.Connection.RemoteIpAddress!.ToString()].Count >
_options.Value.ThresholdCount)
{
_logger.Information(
"[GRPC-MULTI-ACCOUNT-MANAGER] IP: {IP} List Count: {Count}, AgentAddresses: {Agent}",
httpContext.Connection.RemoteIpAddress!.ToString(),
_ipSignerList[httpContext.Connection.RemoteIpAddress!.ToString()].Count,
_ipSignerList[httpContext.Connection.RemoteIpAddress!.ToString()]);
if (!MultiAccountManagementList.ContainsKey(agent))
{
if (!MultiAccountTxIntervalTracker.ContainsKey(agent))
{
_logger.Information(
$"[GRPC-MULTI-ACCOUNT-MANAGER] Adding agent {agent} to the agent tracker.");
MultiAccountTxIntervalTracker.Add(agent, DateTimeOffset.Now);
}
else
{
if ((DateTimeOffset.Now - MultiAccountTxIntervalTracker[agent]).Minutes >=
_options.Value.TxIntervalMinutes)
{
_logger.Information(
$"[GRPC-MULTI-ACCOUNT-MANAGER] Resetting Agent {agent}'s time because " +
$"it has been more than {_options.Value.TxIntervalMinutes} minutes since the last transaction.");
MultiAccountTxIntervalTracker[agent] = DateTimeOffset.Now;
}
else
{
_logger.Information(
$"[GRPC-MULTI-ACCOUNT-MANAGER] Managing Agent {agent} for " +
$"{_options.Value.ManagementTimeMinutes} minutes due to " +
$"{_ipSignerList[httpContext.Connection.RemoteIpAddress!.ToString()].Count} associated accounts.");
ManageMultiAccount(agent);
MultiAccountTxIntervalTracker[agent] = DateTimeOffset.Now;
throw new RpcException(new Status(StatusCode.Cancelled, "Request cancelled."));
}
}
}
else
{
var currentManagedTime = (DateTimeOffset.Now - MultiAccountManagementList[agent]).Minutes;
if (currentManagedTime >= _options.Value.ManagementTimeMinutes)
{
_logger.Information(
$"[GRPC-MULTI-ACCOUNT-MANAGER] Restoring Agent {agent} after {_options.Value.ManagementTimeMinutes} minutes.");
RestoreMultiAccount(agent);
MultiAccountTxIntervalTracker[agent] =
DateTimeOffset.Now.AddMinutes(-_options.Value.TxIntervalMinutes);
_logger.Information(
$"[GRPC-MULTI-ACCOUNT-MANAGER] Current time: {DateTimeOffset.Now} Added time: {DateTimeOffset.Now.AddMinutes(-_options.Value.TxIntervalMinutes)}.");
}
else
{
_logger.Information(
$"[GRPC-MULTI-ACCOUNT-MANAGER] Agent {agent} is in managed status for the next {_options.Value.ManagementTimeMinutes - currentManagedTime} minutes.");
throw new RpcException(new Status(StatusCode.Cancelled, "Request cancelled."));
}
}
}
}
}

return await base.UnaryServerHandler(request, context, continuation);
}
}
}
Loading

0 comments on commit f3f91fd

Please sign in to comment.