diff --git a/NineChronicles.Headless.Tests/Controllers/GraphQLControllerTest.cs b/NineChronicles.Headless.Tests/Controllers/GraphQLControllerTest.cs deleted file mode 100644 index f9daa0754..000000000 --- a/NineChronicles.Headless.Tests/Controllers/GraphQLControllerTest.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reactive.Subjects; -using System.Security.Claims; -using Libplanet.Common; -using Libplanet.Crypto; -using Libplanet.Headless.Hosting; -using Libplanet.Net; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using Nekoyume.Model.State; -using NineChronicles.Headless.Controllers; -using NineChronicles.Headless.GraphTypes; -using NineChronicles.Headless.Properties; -using NineChronicles.Headless.Requests; -using NineChronicles.Headless.Tests.Common; -using Xunit; -using static NineChronicles.Headless.Tests.GraphQLTestUtils; -using IPAddress = System.Net.IPAddress; - -namespace NineChronicles.Headless.Tests.Controllers -{ - public class GraphQLControllerTest - { - private readonly GraphQLController _controller; - private readonly StandaloneContext _standaloneContext; - private readonly IConfiguration _configuration; - private readonly IHttpContextAccessor _httpContextAccessor; - - public GraphQLControllerTest() - { - _standaloneContext = CreateStandaloneContext(); - _configuration = new ConfigurationBuilder().AddInMemoryCollection().Build(); - _httpContextAccessor = new HttpContextAccessor(); - _httpContextAccessor.HttpContext = new DefaultHttpContext(); - ConfigureNineChroniclesNodeService(); - _controller = new GraphQLController(_standaloneContext, _httpContextAccessor, _configuration); - } - - [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void SetMining(bool useSecretToken, bool mine) - { - if (useSecretToken) - { - ConfigureSecretToken(); - ConfigureAdminClaim(); - } - - ConfigureNineChroniclesNodeService(); - Assert.IsType(_controller.SetMining(new SetMiningRequest - { - Mine = mine, - })); - Assert.Equal(mine, _standaloneContext.IsMining); - } - - [Fact] - public void SetMiningThrowsConflict() - { - _standaloneContext.NineChroniclesNodeService = null; - IActionResult result = _controller.SetMining(new SetMiningRequest()); - Assert.IsType(result); - Assert.Equal(StatusCodes.Status409Conflict, ((StatusCodeResult)result).StatusCode); - } - - [Fact] - public void SetMiningThrowsUnauthorizedIfSecretTokenUsed() - { - ConfigureSecretToken(); - Assert.IsType(_controller.SetMining(new SetMiningRequest())); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void SetPrivateKey(bool useSecretToken) - { - if (useSecretToken) - { - ConfigureSecretToken(); - ConfigureAdminClaim(); - } - - ConfigureNineChroniclesNodeService(); - var privateKey = new PrivateKey(); - Assert.IsType(_controller.SetPrivateKey(new SetPrivateKeyRequest - { - PrivateKeyString = ByteUtil.Hex(privateKey.ByteArray), - })); - - Assert.Equal(_standaloneContext.NineChroniclesNodeService!.MinerPrivateKey, privateKey); - } - - [Fact] - public void SetPrivateKeyThrowsConflict() - { - _standaloneContext.NineChroniclesNodeService = null; - IActionResult result = _controller.SetPrivateKey(new SetPrivateKeyRequest()); - Assert.IsType(result); - Assert.Equal(StatusCodes.Status409Conflict, ((StatusCodeResult)result).StatusCode); - } - - [Fact] - public void SetPrivateKeyThrowsUnauthorizedIfSecretTokenUsed() - { - ConfigureSecretToken(); - Assert.IsType(_controller.SetPrivateKey(new SetPrivateKeyRequest())); - } - - [Fact] - public void SetPrivateKeyThrowsBadRequest() - { - ConfigureNineChroniclesNodeService(); - var privateKey = new PrivateKey(); - IActionResult result = _controller.SetPrivateKey(new SetPrivateKeyRequest - { - PrivateKeyString = "test", - }); - Assert.IsType(result); - Assert.Equal(StatusCodes.Status400BadRequest, ((StatusCodeResult)result).StatusCode); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void RemoveSubscribe(bool exist) - { - var address = new PrivateKey().Address; - if (exist) - { - _standaloneContext.AgentAddresses[address] = (new ReplaySubject(), new ReplaySubject(), new ReplaySubject()); - } - - Assert.Equal(exist, _standaloneContext.AgentAddresses.Any()); - _controller.RemoveSubscribe(new AddressRequest - { - AddressString = address.ToHex() - }); - Assert.Empty(_standaloneContext.AgentAddresses); - } - - private string CreateSecretToken() => Guid.NewGuid().ToString(); - - private void ConfigureSecretToken() - { - _configuration[GraphQLService.SecretTokenKey] = CreateSecretToken(); - } - - private void ConfigureAdminClaim() - { - _httpContextAccessor.HttpContext!.User.AddIdentity(new ClaimsIdentity(new[] - { - new Claim("role", "Admin"), - })); - } - - private void ConfigureNineChroniclesNodeService() - { - _standaloneContext.NineChroniclesNodeService = new NineChroniclesNodeService( - new PrivateKey(), - new LibplanetNodeServiceProperties - { - GenesisBlock = _standaloneContext.BlockChain!.Genesis, - StorePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()), - AppProtocolVersion = AppProtocolVersion.Sign(new PrivateKey(), 0), - SwarmPrivateKey = new PrivateKey(), - Host = IPAddress.Loopback.ToString(), - IceServers = new List(), - }, - NineChroniclesNodeService.GetBlockPolicy(NetworkType.Test, StaticActionLoaderSingleton.Instance), - NetworkType.Test, - StaticActionLoaderSingleton.Instance); - } - } -} diff --git a/NineChronicles.Headless/Controllers/GraphQLController.cs b/NineChronicles.Headless/Controllers/GraphQLController.cs deleted file mode 100644 index 70a1591b5..000000000 --- a/NineChronicles.Headless/Controllers/GraphQLController.cs +++ /dev/null @@ -1,285 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading.Tasks; -using Bencodex.Types; -using Libplanet.Common; -using Libplanet.KeyStore; -using Microsoft.AspNetCore.Mvc; -using Nekoyume; -using Nekoyume.Action; -using Nekoyume.Model.State; -using Libplanet.Crypto; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using NineChronicles.Headless.GraphTypes; -using NineChronicles.Headless.Requests; -using Serilog; -using Lib9c.Renderers; -using Libplanet.Action.State; -using System.Collections.Immutable; - -namespace NineChronicles.Headless.Controllers -{ - [ApiController] - public class GraphQLController : ControllerBase - { - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IConfiguration _configuration; - - private ConcurrentDictionary NotificationRecords { get; } - = new ConcurrentDictionary(); - private StandaloneContext StandaloneContext { get; } - - public const string SetPrivateKeyEndpoint = "/set-private-key"; - - public const string SetMiningEndpoint = "/set-mining"; - - public const string CheckPeerEndpoint = "/check-peer"; - - public const string RemoveSubscribeEndPoint = "/remove-subscribe"; - - public GraphQLController(StandaloneContext standaloneContext, IHttpContextAccessor httpContextAccessor, IConfiguration configuration) - { - _httpContextAccessor = httpContextAccessor; - _configuration = configuration; - StandaloneContext = standaloneContext; - } - - [HttpPost(SetPrivateKeyEndpoint)] - public IActionResult SetPrivateKey([FromBody] SetPrivateKeyRequest request) - { - if (!HasLocalPolicy()) - { - return Unauthorized(); - } - - if (StandaloneContext.NineChroniclesNodeService is null) - { - // Waiting node service. - return new StatusCodeResult(StatusCodes.Status409Conflict); - } - - try - { - // For users with private keys less than 32 bytes in length, increase this accordingly. - var destArray = new byte[32]; - var srcArray = ByteUtil.ParseHex(request.PrivateKeyString); - Array.Copy(srcArray, 0, destArray, destArray.Length - srcArray.Length, srcArray.Length); - var privateKey = new PrivateKey(destArray); - StandaloneContext.NineChroniclesNodeService.MinerPrivateKey = privateKey; - var msg = - $"Private key set ({StandaloneContext.NineChroniclesNodeService.MinerPrivateKey.PublicKey.Address})."; - Log.Information("SetPrivateKey: {Msg}", msg); - return Ok(msg); - } - catch - { - return new StatusCodeResult(StatusCodes.Status400BadRequest); - } - } - - // Should deprecate this endpoint after release v200000. - [HttpPost(SetMiningEndpoint)] - public IActionResult SetMining([FromBody] SetMiningRequest request) - { - if (!HasLocalPolicy()) - { - return Unauthorized(); - } - - if (StandaloneContext.NineChroniclesNodeService is null) - { - // Waiting node service. - return new StatusCodeResult(StatusCodes.Status409Conflict); - } - - StandaloneContext.IsMining = request.Mine; - - return Ok(); - } - - [HttpPost(CheckPeerEndpoint)] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status409Conflict)] - [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task CheckPeer([FromBody] AddressRequest request) - { - if (StandaloneContext.NineChroniclesNodeService is null) - { - // Waiting node service. - return new StatusCodeResult(StatusCodes.Status409Conflict); - } - - try - { - var exist = await StandaloneContext.NineChroniclesNodeService.CheckPeer(request.AddressString); - if (exist) - { - return Ok($"Found peer {request.AddressString}."); - } - - return BadRequest($"No such peer {request.AddressString}"); - } - catch (Exception e) - { - var msg = $"Unexpected error occurred during CheckPeer request. {e}"; - // Unexpected Error. - Log.Warning(e, msg); - return StatusCode(StatusCodes.Status500InternalServerError, msg); - } - } - - [HttpPost(RemoveSubscribeEndPoint)] - public IActionResult RemoveSubscribe([FromBody] AddressRequest request) - { - if (!HasLocalPolicy()) - { - return Unauthorized(); - } - - var address = new Address(request.AddressString); - StandaloneContext.AgentAddresses.TryRemove(address, out _); - return Ok(200); - } - - //TODO : This should be covered in test. - private void NotifyRefillActionPoint(long newTipIndex) - { - if (StandaloneContext.KeyStore is null) - { - throw new InvalidOperationException($"{nameof(StandaloneContext.KeyStore)} is null."); - } - - List> tuples = - StandaloneContext.KeyStore.List().ToList(); - if (!tuples.Any()) - { - return; - } - - IEnumerable
playerAddresses = tuples.Select(tuple => tuple.Item2.Address); - var chain = StandaloneContext.BlockChain; - if (chain is null) - { - throw new InvalidOperationException($"{nameof(chain)} is null."); - } - - List states = playerAddresses - .Select(addr => chain.GetState(addr)) - .Where(value => !(value is null)) - .ToList(); - - if (!states.Any()) - { - return; - } - - var agentStates = - states.Select(state => new AgentState((Bencodex.Types.Dictionary)state)); - var avatarStates = agentStates.SelectMany(agentState => - agentState.avatarAddresses.Values.Select(address => - new AvatarState((Bencodex.Types.Dictionary)chain.GetState(address)))); - var gameConfigState = - new GameConfigState((Bencodex.Types.Dictionary)chain.GetState(Addresses.GameConfig)); - - bool IsDailyRewardRefilled(long dailyRewardReceivedIndex) - { - return newTipIndex >= dailyRewardReceivedIndex + gameConfigState.DailyRewardInterval; - } - - bool NeedsRefillNotification(AvatarState avatarState) - { - if (NotificationRecords.TryGetValue(avatarState.address, out long record)) - { - return avatarState.dailyRewardReceivedIndex != record - && IsDailyRewardRefilled(avatarState.dailyRewardReceivedIndex); - } - - return IsDailyRewardRefilled(avatarState.dailyRewardReceivedIndex); - } - - var avatarStatesToSendNotification = avatarStates - .Where(NeedsRefillNotification) - .ToList(); - - if (avatarStatesToSendNotification.Any()) - { - var notification = new Notification(NotificationEnum.Refill); - StandaloneContext.NotificationSubject.OnNext(notification); - } - - foreach (var avatarState in avatarStatesToSendNotification) - { - Log.Debug( - "Record notification for {AvatarAddress}", - avatarState.address.ToHex()); - NotificationRecords[avatarState.address] = avatarState.dailyRewardReceivedIndex; - } - } - - private void NotifyAction(ActionEvaluation eval) - { - if (StandaloneContext.NineChroniclesNodeService is null) - { - throw new InvalidOperationException( - $"{nameof(StandaloneContext.NineChroniclesNodeService)} is null."); - } - - if (StandaloneContext.NineChroniclesNodeService.MinerPrivateKey is null) - { - Log.Information("PrivateKey is not set. please call SetPrivateKey() first."); - return; - } - Address address = StandaloneContext.NineChroniclesNodeService.MinerPrivateKey.PublicKey.Address; - var input = StandaloneContext.NineChroniclesNodeService.BlockChain.GetAccountState(eval.PreviousState); - var output = StandaloneContext.NineChroniclesNodeService.BlockChain.GetAccountState(eval.OutputState); - var diff = AccountDiff.Create(input, output); - var updatedAddresses = diff.FungibleAssetValueDiffs - .Select(pair => pair.Key.Item1) - .Concat(diff.StateDiffs.Keys).ToImmutableHashSet(); - if (updatedAddresses.Contains(address) || eval.Signer == address) - { - if (eval.Signer == address) - { - var type = NotificationEnum.Refill; - var msg = string.Empty; - switch (eval.Action) - { - case HackAndSlash4 has: - type = NotificationEnum.HAS; - msg = has.stageId.ToString(CultureInfo.InvariantCulture); - break; - case CombinationConsumable3 _: - type = NotificationEnum.CombinationConsumable; - break; - case CombinationEquipment4 _: - type = NotificationEnum.CombinationEquipment; - break; - case Buy4 _: - type = NotificationEnum.Buyer; - break; - } - Log.Information("NotifyAction: Type: {Type} MSG: {Msg}", type, msg); - var notification = new Notification(type, msg); - StandaloneContext.NotificationSubject.OnNext(notification); - } - else - { - if (eval.Action is Buy4 buy && buy.sellerAgentAddress == address) - { - var notification = new Notification(NotificationEnum.Seller); - StandaloneContext.NotificationSubject.OnNext(notification); - } - } - } - } - - // FIXME: remove this method with DI. - private bool HasLocalPolicy() => !(_configuration[GraphQLService.SecretTokenKey] is { }) || - (_httpContextAccessor.HttpContext?.User.HasClaim("role", "Admin") ?? false); - } -}