From 8fe0e577448fdfe3a9fd2320d6a6bb8c4f3c62fd Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Tue, 23 Feb 2021 12:08:26 +0900 Subject: [PATCH 01/21] Fix NotifyAction subscribe --- .../Controllers/GraphQLController.cs | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/NineChronicles.Headless/Controllers/GraphQLController.cs b/NineChronicles.Headless/Controllers/GraphQLController.cs index fd9431718..ff4fd5a82 100644 --- a/NineChronicles.Headless/Controllers/GraphQLController.cs +++ b/NineChronicles.Headless/Controllers/GraphQLController.cs @@ -32,7 +32,6 @@ public class GraphQLController : ControllerBase private ConcurrentDictionary NotificationRecords { get; } = new ConcurrentDictionary(); private StandaloneContext StandaloneContext { get; } - private Address _address; public const string RunStandaloneEndpoint = "/run-standalone"; @@ -115,9 +114,8 @@ public IActionResult SetPrivateKey([FromBody] SetPrivateKeyRequest request) var privateKey = new PrivateKey(ByteUtil.ParseHex(request.PrivateKeyString)); StandaloneContext.NineChroniclesNodeService.PrivateKey = privateKey; - _address = privateKey.PublicKey.ToAddress(); - var msg = $"Private key set ({privateKey.PublicKey.ToAddress()})."; - Log.Debug(msg); + var msg = $"Private key set ({StandaloneContext.NineChroniclesNodeService.PrivateKey.PublicKey.ToAddress()})."; + Log.Information("SetPrivateKey: {Msg}", msg); return Ok(msg); } @@ -249,34 +247,40 @@ bool NeedsRefillNotification(AvatarState avatarState) private void NotifyAction(ActionBase.ActionEvaluation eval) { - if (eval.OutputStates.UpdatedAddresses.Contains(_address)) + if (StandaloneContext.NineChroniclesNodeService.PrivateKey is null) { - if (eval.Signer == _address) + return; + } + var address = StandaloneContext.NineChroniclesNodeService.PrivateKey.PublicKey.ToAddress(); + if (eval.OutputStates.UpdatedAddresses.Contains(address) || eval.Signer == address) + { + if (eval.Signer == address) { var type = NotificationEnum.Refill; var msg = string.Empty; switch (eval.Action) { - case HackAndSlash3 has: + case HackAndSlash4 has: type = NotificationEnum.HAS; msg = has.stageId.ToString(CultureInfo.InvariantCulture); break; - case CombinationConsumable2 _: + case CombinationConsumable3 _: type = NotificationEnum.CombinationConsumable; break; - case CombinationEquipment3 _: + case CombinationEquipment4 _: type = NotificationEnum.CombinationEquipment; break; - case Buy3 _: + 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 Buy3 buy && buy.sellerAgentAddress == _address) + if (eval.Action is Buy4 buy && buy.sellerAgentAddress == address) { var notification = new Notification(NotificationEnum.Seller); StandaloneContext.NotificationSubject.OnNext(notification); From d1d4e9fd72d4789c2c01cfe515f2a733b0bdcd89 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Tue, 23 Feb 2021 14:10:45 +0900 Subject: [PATCH 02/21] Review applied. --- NineChronicles.Headless/Controllers/GraphQLController.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NineChronicles.Headless/Controllers/GraphQLController.cs b/NineChronicles.Headless/Controllers/GraphQLController.cs index ff4fd5a82..19e33ca9b 100644 --- a/NineChronicles.Headless/Controllers/GraphQLController.cs +++ b/NineChronicles.Headless/Controllers/GraphQLController.cs @@ -249,9 +249,10 @@ private void NotifyAction(ActionBase.ActionEvaluation eval) { if (StandaloneContext.NineChroniclesNodeService.PrivateKey is null) { + Log.Information("PrivateKey is not set. please call SetPrivateKey() first."); return; } - var address = StandaloneContext.NineChroniclesNodeService.PrivateKey.PublicKey.ToAddress(); + Address address = StandaloneContext.NineChroniclesNodeService.PrivateKey.PublicKey.ToAddress(); if (eval.OutputStates.UpdatedAddresses.Contains(address) || eval.Signer == address) { if (eval.Signer == address) From b6e25b07fac01478dfe077a630b6372345102087 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Tue, 23 Feb 2021 15:53:47 +0900 Subject: [PATCH 03/21] Add description on StateQuery --- NineChronicles.Headless/GraphTypes/StateQuery.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/NineChronicles.Headless/GraphTypes/StateQuery.cs b/NineChronicles.Headless/GraphTypes/StateQuery.cs index 11ffb67a1..c67a722de 100644 --- a/NineChronicles.Headless/GraphTypes/StateQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StateQuery.cs @@ -16,9 +16,11 @@ public StateQuery() Name = "StateQuery"; Field( name: "avatar", + description: "State for character.", arguments: new QueryArguments(new QueryArgument { Name = "address", + Description = "Address of AvatarState." }), resolve: context => { @@ -27,10 +29,12 @@ public StateQuery() }); Field( name: "rankingMap", + description: "State for Record AvatarState EXP.", arguments: new QueryArguments( new QueryArgument> { Name = "index", + Description = "RankingMapState index. 0 ~ 99" }), resolve: context => { @@ -39,13 +43,16 @@ public StateQuery() }); Field( name: "shop", + description: "State for market.", resolve: context => new ShopState((Dictionary) context.Source(Addresses.Shop))); Field( name: "weeklyArena", + description: "State for arena.", arguments: new QueryArguments( new QueryArgument> { Name = "index", + Description = "WeeklyArenaState index. It increases every 56,000 blocks." }), resolve: context => { @@ -55,9 +62,11 @@ public StateQuery() }); Field( name: "agent", + description: "State for account.", arguments: new QueryArguments(new QueryArgument> { Name = "address", + Description = "Address of AgentState." }), resolve: context => { From c0402e72a4391aaa0e924a91500adcf9ab3b94cd Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Tue, 23 Feb 2021 17:46:54 +0900 Subject: [PATCH 04/21] Add AvatarState description --- .../GraphTypes/StateQuery.cs | 2 +- .../GraphTypes/States/AvatarStateType.cs | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/NineChronicles.Headless/GraphTypes/StateQuery.cs b/NineChronicles.Headless/GraphTypes/StateQuery.cs index c67a722de..79dcc7685 100644 --- a/NineChronicles.Headless/GraphTypes/StateQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StateQuery.cs @@ -16,7 +16,7 @@ public StateQuery() Name = "StateQuery"; Field( name: "avatar", - description: "State for character.", + description: "State for avatar.", arguments: new QueryArguments(new QueryArgument { Name = "address", diff --git a/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs b/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs index 32a3f7c67..98c78143e 100644 --- a/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs +++ b/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs @@ -14,79 +14,103 @@ public AvatarStateType() { Field>( nameof(AvatarState.address), + description: "Address of AvatarState.", resolve: context => context.Source.address); Field>( nameof(AvatarState.blockIndex), + description: "Block index at latest action was executed.", resolve: context => context.Source.blockIndex); Field>( nameof(AvatarState.characterId), + description: "Character ID from CharacterSheet.", resolve: context => context.Source.characterId); Field>( nameof(AvatarState.dailyRewardReceivedIndex), + description: "Block index at DailyReward was executed.", resolve: context => context.Source.dailyRewardReceivedIndex); Field>( nameof(AvatarState.agentAddress), + description: "Address of AgentState.", resolve: context => context.Source.agentAddress); Field>( nameof(AvatarState.RankingMapAddress), + description: "Address of the RankingMapState where this avatar information is recorded.", resolve: context => context.Source.RankingMapAddress); Field>( nameof(AvatarState.updatedAt), + description: "Block index at latest action was executed.", resolve: context => context.Source.updatedAt); Field>( nameof(AvatarState.name), + description: "AvatarState name.", resolve: context => context.Source.name); Field>( nameof(AvatarState.exp), + description: "AvatarState total EXP.", resolve: context => context.Source.exp); Field>( nameof(AvatarState.level), + description: "AvatarState Level.", resolve: context => context.Source.level); Field>( nameof(AvatarState.actionPoint), + description: "Current ActionPoint.", resolve: context => context.Source.actionPoint); Field>( nameof(AvatarState.ear), + description: "Index of ear color.", resolve: context => context.Source.ear); Field>( nameof(AvatarState.hair), + description: "Index of hair color.", resolve: context => context.Source.hair); Field>( nameof(AvatarState.lens), + description: "Index of eye color.", resolve: context => context.Source.lens); Field>( nameof(AvatarState.tail), + description: "Index of tail color.", resolve: context => context.Source.tail); Field>( nameof(AvatarState.inventory), + description: "AvatarState inventory.", resolve: context => context.Source.inventory); Field>>>( nameof(AvatarState.combinationSlotAddresses), + description: "Address list of combination slot.", resolve: context => context.Source.combinationSlotAddresses); Field>( nameof(AvatarState.itemMap), + description: "List of acquired item ID.", resolve: context => context.Source.itemMap); Field>( nameof(AvatarState.eventMap), + description: "List of quest event ID.", resolve: context => context.Source.eventMap); Field>( nameof(AvatarState.monsterMap), + description: "List of defeated monster ID.", resolve: context => context.Source.monsterMap); Field>( nameof(AvatarState.stageMap), + description: "List of cleared stage ID.", resolve: context => context.Source.stageMap); Field>( nameof(AvatarState.questList), + description: "List of quest.", resolve: context => context.Source.questList); Field>( nameof(AvatarState.mailBox), + description: "List of mail.", resolve: context => context.Source.mailBox); Field>( nameof(AvatarState.worldInformation), + description: "World & Stage information.", resolve: context => context.Source.worldInformation); } } From 2929b95a1a7087de1c671b7509b874da9f6a4495 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Tue, 23 Feb 2021 17:57:20 +0900 Subject: [PATCH 05/21] Add AgentState description --- NineChronicles.Headless/GraphTypes/States/AgentStateType.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/NineChronicles.Headless/GraphTypes/States/AgentStateType.cs b/NineChronicles.Headless/GraphTypes/States/AgentStateType.cs index a480f81ac..20783b027 100644 --- a/NineChronicles.Headless/GraphTypes/States/AgentStateType.cs +++ b/NineChronicles.Headless/GraphTypes/States/AgentStateType.cs @@ -16,11 +16,15 @@ public AgentStateType(StandaloneContext standaloneContext) { Field>( nameof(AgentState.address), + description: "Address of agent.", resolve: context => context.Source.address); Field>>( nameof(AgentState.avatarAddresses), + description: "Address list of avatar.", resolve: context => context.Source.avatarAddresses.Select(a => a.Value)); - Field>("gold", + Field>( + "gold", + description: "Current NCG.", resolve: context => { if (!(standaloneContext.BlockChain is BlockChain> blockChain)) From dd621a5c83342e092a10489fe535bf4e0cb2f2f6 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Tue, 23 Feb 2021 18:29:25 +0900 Subject: [PATCH 06/21] Add RankingMapState description --- .../GraphTypes/States/RankingInfoType.cs | 8 ++++++++ .../GraphTypes/States/RankingMapStateType.cs | 3 +++ 2 files changed, 11 insertions(+) diff --git a/NineChronicles.Headless/GraphTypes/States/RankingInfoType.cs b/NineChronicles.Headless/GraphTypes/States/RankingInfoType.cs index 8f46a3fc6..9a64ec76f 100644 --- a/NineChronicles.Headless/GraphTypes/States/RankingInfoType.cs +++ b/NineChronicles.Headless/GraphTypes/States/RankingInfoType.cs @@ -9,27 +9,35 @@ public RankingInfoType() { Field>( nameof(RankingInfo.Exp), + description: "AvatarState total EXP.", resolve: context => context.Source.Exp); Field>( nameof(RankingInfo.Level), + description: "AvatarState Level.", resolve: context => context.Source.Level); Field>( nameof(RankingInfo.ArmorId), + description: "Equipped Armor id from EquipmentItemSheet.", resolve: context => context.Source.ArmorId); Field>( nameof(RankingInfo.UpdatedAt), + description: "RankingInfo updated block index.", resolve: context => context.Source.UpdatedAt); Field>( nameof(RankingInfo.StageClearedBlockIndex), + description: "Latest stage cleared block index.", resolve: context => context.Source.StageClearedBlockIndex); Field>( nameof(RankingInfo.AgentAddress), + description: "Address of AgentState.", resolve: context => context.Source.AgentAddress); Field>( nameof(RankingInfo.AvatarAddress), + description: "Address of AvatarState.", resolve: context => context.Source.AvatarAddress); Field>( nameof(RankingInfo.AvatarName), + description: "AvatarState name.", resolve: context => context.Source.AvatarName); } } diff --git a/NineChronicles.Headless/GraphTypes/States/RankingMapStateType.cs b/NineChronicles.Headless/GraphTypes/States/RankingMapStateType.cs index d92b64398..f1906d0b7 100644 --- a/NineChronicles.Headless/GraphTypes/States/RankingMapStateType.cs +++ b/NineChronicles.Headless/GraphTypes/States/RankingMapStateType.cs @@ -10,12 +10,15 @@ public RankingMapStateType() { Field>( nameof(RankingMapState.address), + description: "Address of RankingMapState.", resolve: context => context.Source.address); Field>( nameof(RankingMapState.Capacity), + description: "RankingMapState Capacity.", resolve: context => RankingMapState.Capacity); Field>>>( "rankingInfos", + description: "List of RankingInfo.", resolve: context => context.Source.GetRankingInfos(null)); } } From 8e7bfaf761cb8bd3061cb25e9cee74e7edd5482c Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Tue, 23 Feb 2021 18:47:24 +0900 Subject: [PATCH 07/21] Add ShopState description --- .../States/Models/Item/CostumeType.cs | 10 +++++-- .../States/Models/Item/ItemBaseType.cs | 26 +++++++++++++++---- .../States/Models/Item/ItemUsableType.cs | 5 +++- .../States/Models/Item/ShopItemType.cs | 6 +++++ .../GraphTypes/States/ShopStateType.cs | 2 ++ 5 files changed, 41 insertions(+), 8 deletions(-) diff --git a/NineChronicles.Headless/GraphTypes/States/Models/Item/CostumeType.cs b/NineChronicles.Headless/GraphTypes/States/Models/Item/CostumeType.cs index 2431393cf..01bff130b 100644 --- a/NineChronicles.Headless/GraphTypes/States/Models/Item/CostumeType.cs +++ b/NineChronicles.Headless/GraphTypes/States/Models/Item/CostumeType.cs @@ -7,8 +7,14 @@ public class CostumeType : ItemBaseType { public CostumeType() { - Field>(nameof(Costume.ItemId)); - Field>(nameof(Costume.Equipped)); + Field>( + nameof(Costume.ItemId), + description: "Guid of costume." + ); + Field>( + nameof(Costume.Equipped), + description: "Status of Avatar equipped." + ); } } } diff --git a/NineChronicles.Headless/GraphTypes/States/Models/Item/ItemBaseType.cs b/NineChronicles.Headless/GraphTypes/States/Models/Item/ItemBaseType.cs index b52bb4ced..ac8ace758 100644 --- a/NineChronicles.Headless/GraphTypes/States/Models/Item/ItemBaseType.cs +++ b/NineChronicles.Headless/GraphTypes/States/Models/Item/ItemBaseType.cs @@ -9,12 +9,28 @@ public abstract class ItemBaseType : ObjectGraphType { protected ItemBaseType() { - Field>(nameof(ItemBase.Grade)); - Field>(nameof(ItemBase.Id)); + Field>( + nameof(ItemBase.Grade), + description: "Grade from ItemSheet." + ); + Field>( + nameof(ItemBase.Id), + description: "Id from ItemSheet." + ); - Field>(nameof(ItemBase.ItemType), resolve: context => context.Source.ItemType); - Field>(nameof(ItemBase.ItemSubType)); - Field>(nameof(ItemBase.ElementalType)); + Field>( + nameof(ItemBase.ItemType), + description: "Item category.", + resolve: context => context.Source.ItemType + ); + Field>( + nameof(ItemBase.ItemSubType), + description: "Item subcategory." + ); + Field>( + nameof(ItemBase.ElementalType), + description: "Item elemental." + ); } } } diff --git a/NineChronicles.Headless/GraphTypes/States/Models/Item/ItemUsableType.cs b/NineChronicles.Headless/GraphTypes/States/Models/Item/ItemUsableType.cs index 90b6f325a..f2f5cd33b 100644 --- a/NineChronicles.Headless/GraphTypes/States/Models/Item/ItemUsableType.cs +++ b/NineChronicles.Headless/GraphTypes/States/Models/Item/ItemUsableType.cs @@ -7,7 +7,10 @@ public class ItemUsableType : ItemBaseType { public ItemUsableType() : base() { - Field>(nameof(ItemUsable.ItemId)); + Field>( + nameof(ItemUsable.ItemId), + description: "Guid of item." + ); } } } diff --git a/NineChronicles.Headless/GraphTypes/States/Models/Item/ShopItemType.cs b/NineChronicles.Headless/GraphTypes/States/Models/Item/ShopItemType.cs index 9273ccaf5..2280e1499 100644 --- a/NineChronicles.Headless/GraphTypes/States/Models/Item/ShopItemType.cs +++ b/NineChronicles.Headless/GraphTypes/States/Models/Item/ShopItemType.cs @@ -9,23 +9,29 @@ public ShopItemType() { Field>( nameof(ShopItem.SellerAgentAddress), + description: "Address of seller agent.", resolve: context => context.Source.SellerAgentAddress); Field>( nameof(ShopItem.SellerAvatarAddress), + description: "Address of seller avatar.", resolve: context => context.Source.SellerAvatarAddress); Field>( nameof(ShopItem.ProductId), + description: "Guid of shop registered.", resolve: context => context.Source.ProductId); Field>( nameof(ShopItem.Price), + description: "Item price.", resolve: context => context.Source.Price ); Field( nameof(ShopItem.ItemUsable), + description: "Equipment / Consumable information.", resolve: context => context.Source.ItemUsable ); Field( nameof(ShopItem.Costume), + description: "Costume information.", resolve: context => context.Source.Costume ); } diff --git a/NineChronicles.Headless/GraphTypes/States/ShopStateType.cs b/NineChronicles.Headless/GraphTypes/States/ShopStateType.cs index 83b84e459..79800d33d 100644 --- a/NineChronicles.Headless/GraphTypes/States/ShopStateType.cs +++ b/NineChronicles.Headless/GraphTypes/States/ShopStateType.cs @@ -15,9 +15,11 @@ public ShopStateType() { Field>( nameof(ShopState.address), + description: "Address of shop.", resolve: context => context.Source.address); Field>>( nameof(ShopState.Products), + description: "List of ShopItem.", arguments: new QueryArguments( new QueryArgument { From 2b326c102ef35503cb1113cea73399a44b2004d8 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Tue, 23 Feb 2021 18:49:30 +0900 Subject: [PATCH 08/21] Add inventory description --- .../States/Models/Item/InventoryType.cs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/NineChronicles.Headless/GraphTypes/States/Models/Item/InventoryType.cs b/NineChronicles.Headless/GraphTypes/States/Models/Item/InventoryType.cs index 1ff07a7d8..c82dd3234 100644 --- a/NineChronicles.Headless/GraphTypes/States/Models/Item/InventoryType.cs +++ b/NineChronicles.Headless/GraphTypes/States/Models/Item/InventoryType.cs @@ -7,10 +7,22 @@ public class InventoryType : ObjectGraphType { public InventoryType() { - Field>>>(nameof(Inventory.Consumables)); - Field>>>(nameof(Inventory.Materials)); - Field>>>(nameof(Inventory.Equipments)); - Field>>>(nameof(Inventory.Costumes)); + Field>>>( + nameof(Inventory.Consumables), + description: "List of Consumable." + ); + Field>>>( + nameof(Inventory.Materials), + description: "List of Material." + ); + Field>>>( + nameof(Inventory.Equipments), + description: "List of Equipment." + ); + Field>>>( + nameof(Inventory.Costumes), + description: "List of Costumez." + ); } } } From be9dcc8d2a810e43eaa54f8a0dcb2ac70b703608 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Wed, 24 Feb 2021 18:17:40 +0900 Subject: [PATCH 09/21] Fix Typo --- .../GraphTypes/StateQuery.cs | 12 ++++++------ .../GraphTypes/States/AvatarStateType.cs | 18 +++++++++--------- .../States/Models/Item/InventoryType.cs | 8 ++++---- .../States/Models/Item/ItemBaseType.cs | 2 +- .../States/Models/Item/ShopItemType.cs | 2 +- .../GraphTypes/States/RankingInfoType.cs | 16 ++++++++-------- 6 files changed, 29 insertions(+), 29 deletions(-) diff --git a/NineChronicles.Headless/GraphTypes/StateQuery.cs b/NineChronicles.Headless/GraphTypes/StateQuery.cs index 79dcc7685..dbfd5535a 100644 --- a/NineChronicles.Headless/GraphTypes/StateQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StateQuery.cs @@ -20,7 +20,7 @@ public StateQuery() arguments: new QueryArguments(new QueryArgument { Name = "address", - Description = "Address of AvatarState." + Description = "Address of avatar." }), resolve: context => { @@ -29,7 +29,7 @@ public StateQuery() }); Field( name: "rankingMap", - description: "State for Record AvatarState EXP.", + description: "State for avatar EXP record.", arguments: new QueryArguments( new QueryArgument> { @@ -43,11 +43,11 @@ public StateQuery() }); Field( name: "shop", - description: "State for market.", + description: "State for shop.", resolve: context => new ShopState((Dictionary) context.Source(Addresses.Shop))); Field( name: "weeklyArena", - description: "State for arena.", + description: "State for weekly arena.", arguments: new QueryArguments( new QueryArgument> { @@ -62,11 +62,11 @@ public StateQuery() }); Field( name: "agent", - description: "State for account.", + description: "State for agent.", arguments: new QueryArguments(new QueryArgument> { Name = "address", - Description = "Address of AgentState." + Description = "Address of agent." }), resolve: context => { diff --git a/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs b/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs index 98c78143e..f1093e831 100644 --- a/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs +++ b/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs @@ -14,11 +14,11 @@ public AvatarStateType() { Field>( nameof(AvatarState.address), - description: "Address of AvatarState.", + description: "Address of avatar.", resolve: context => context.Source.address); Field>( nameof(AvatarState.blockIndex), - description: "Block index at latest action was executed.", + description: "Block index at the latest executed action.", resolve: context => context.Source.blockIndex); Field>( nameof(AvatarState.characterId), @@ -26,11 +26,11 @@ public AvatarStateType() resolve: context => context.Source.characterId); Field>( nameof(AvatarState.dailyRewardReceivedIndex), - description: "Block index at DailyReward was executed.", + description: "Block index at the DailyReward execution.", resolve: context => context.Source.dailyRewardReceivedIndex); Field>( nameof(AvatarState.agentAddress), - description: "Address of AgentState.", + description: "Address of agent.", resolve: context => context.Source.agentAddress); Field>( nameof(AvatarState.RankingMapAddress), @@ -38,20 +38,20 @@ public AvatarStateType() resolve: context => context.Source.RankingMapAddress); Field>( nameof(AvatarState.updatedAt), - description: "Block index at latest action was executed.", + description: "Block index at the latest executed action.", resolve: context => context.Source.updatedAt); Field>( nameof(AvatarState.name), - description: "AvatarState name.", + description: "Avatar name.", resolve: context => context.Source.name); Field>( nameof(AvatarState.exp), - description: "AvatarState total EXP.", + description: "Avatar total EXP.", resolve: context => context.Source.exp); Field>( nameof(AvatarState.level), - description: "AvatarState Level.", + description: "Avatar Level.", resolve: context => context.Source.level); Field>( nameof(AvatarState.actionPoint), @@ -77,7 +77,7 @@ public AvatarStateType() Field>( nameof(AvatarState.inventory), - description: "AvatarState inventory.", + description: "Avatar inventory.", resolve: context => context.Source.inventory); Field>>>( nameof(AvatarState.combinationSlotAddresses), diff --git a/NineChronicles.Headless/GraphTypes/States/Models/Item/InventoryType.cs b/NineChronicles.Headless/GraphTypes/States/Models/Item/InventoryType.cs index c82dd3234..4d9ae1f96 100644 --- a/NineChronicles.Headless/GraphTypes/States/Models/Item/InventoryType.cs +++ b/NineChronicles.Headless/GraphTypes/States/Models/Item/InventoryType.cs @@ -9,19 +9,19 @@ public InventoryType() { Field>>>( nameof(Inventory.Consumables), - description: "List of Consumable." + description: "List of Consumables." ); Field>>>( nameof(Inventory.Materials), - description: "List of Material." + description: "List of Materials." ); Field>>>( nameof(Inventory.Equipments), - description: "List of Equipment." + description: "List of Equipments." ); Field>>>( nameof(Inventory.Costumes), - description: "List of Costumez." + description: "List of Costumes." ); } } diff --git a/NineChronicles.Headless/GraphTypes/States/Models/Item/ItemBaseType.cs b/NineChronicles.Headless/GraphTypes/States/Models/Item/ItemBaseType.cs index ac8ace758..0529f255c 100644 --- a/NineChronicles.Headless/GraphTypes/States/Models/Item/ItemBaseType.cs +++ b/NineChronicles.Headless/GraphTypes/States/Models/Item/ItemBaseType.cs @@ -15,7 +15,7 @@ protected ItemBaseType() ); Field>( nameof(ItemBase.Id), - description: "Id from ItemSheet." + description: "ID from ItemSheet." ); Field>( diff --git a/NineChronicles.Headless/GraphTypes/States/Models/Item/ShopItemType.cs b/NineChronicles.Headless/GraphTypes/States/Models/Item/ShopItemType.cs index 2280e1499..3ba2abc6f 100644 --- a/NineChronicles.Headless/GraphTypes/States/Models/Item/ShopItemType.cs +++ b/NineChronicles.Headless/GraphTypes/States/Models/Item/ShopItemType.cs @@ -17,7 +17,7 @@ public ShopItemType() resolve: context => context.Source.SellerAvatarAddress); Field>( nameof(ShopItem.ProductId), - description: "Guid of shop registered.", + description: "Guid of product registered.", resolve: context => context.Source.ProductId); Field>( nameof(ShopItem.Price), diff --git a/NineChronicles.Headless/GraphTypes/States/RankingInfoType.cs b/NineChronicles.Headless/GraphTypes/States/RankingInfoType.cs index 9a64ec76f..781de8d8e 100644 --- a/NineChronicles.Headless/GraphTypes/States/RankingInfoType.cs +++ b/NineChronicles.Headless/GraphTypes/States/RankingInfoType.cs @@ -9,35 +9,35 @@ public RankingInfoType() { Field>( nameof(RankingInfo.Exp), - description: "AvatarState total EXP.", + description: "Avatar total EXP.", resolve: context => context.Source.Exp); Field>( nameof(RankingInfo.Level), - description: "AvatarState Level.", + description: "Avatar Level.", resolve: context => context.Source.Level); Field>( nameof(RankingInfo.ArmorId), - description: "Equipped Armor id from EquipmentItemSheet.", + description: "Equipped Armor ID from EquipmentItemSheet.", resolve: context => context.Source.ArmorId); Field>( nameof(RankingInfo.UpdatedAt), - description: "RankingInfo updated block index.", + description: "Block index at RankingInfo update.", resolve: context => context.Source.UpdatedAt); Field>( nameof(RankingInfo.StageClearedBlockIndex), - description: "Latest stage cleared block index.", + description: "Block index at Latest stage cleared.", resolve: context => context.Source.StageClearedBlockIndex); Field>( nameof(RankingInfo.AgentAddress), - description: "Address of AgentState.", + description: "Address of agent.", resolve: context => context.Source.AgentAddress); Field>( nameof(RankingInfo.AvatarAddress), - description: "Address of AvatarState.", + description: "Address of avatar.", resolve: context => context.Source.AvatarAddress); Field>( nameof(RankingInfo.AvatarName), - description: "AvatarState name.", + description: "Avatar name.", resolve: context => context.Source.AvatarName); } } From ebe304d1cc7798a6909653902fd8a5371b3a252b Mon Sep 17 00:00:00 2001 From: Kidon Seo Date: Wed, 24 Feb 2021 18:25:37 +0900 Subject: [PATCH 10/21] update docker workflow --- .github/workflows/push_docker_image.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/push_docker_image.yml b/.github/workflows/push_docker_image.yml index 7243e84e9..5fefe001c 100644 --- a/.github/workflows/push_docker_image.yml +++ b/.github/workflows/push_docker_image.yml @@ -5,6 +5,7 @@ on: branches: - main - 9c-main + - development jobs: build_and_push: @@ -17,7 +18,7 @@ jobs: - name: login run: docker login --username '${{ secrets.DOCKER_USERNAME }}' --password '${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}' - name: build - run: docker build . -t planetariumhq/ninechronicles-headless:git-${{ github.sha }} --build-arg COMMIT=git-${{ github.sha }} --build-arg COMMIT=git-${{ github.sha }} + run: docker build . -t planetariumhq/ninechronicles-headless:git-${{ github.sha }} --build-arg COMMIT=git-${{ github.sha }} - name: push git-version run: docker push planetariumhq/ninechronicles-headless:git-${{ github.sha }} From e758f5c32eee2618f4d2e2eff8c08bae87cff2df Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Wed, 24 Feb 2021 18:58:02 +0900 Subject: [PATCH 11/21] Update ActionMutation description --- .../GraphTypes/ActionMutation.cs | 54 +++++++++++-------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/NineChronicles.Headless/GraphTypes/ActionMutation.cs b/NineChronicles.Headless/GraphTypes/ActionMutation.cs index 8ac09644f..9a558c134 100644 --- a/NineChronicles.Headless/GraphTypes/ActionMutation.cs +++ b/NineChronicles.Headless/GraphTypes/ActionMutation.cs @@ -21,11 +21,12 @@ public class ActionMutation : ObjectGraphType public ActionMutation() { Field>("createAvatar", + description: "Create new avatar.", arguments: new QueryArguments( new QueryArgument> { Name = "avatarName", - Description = "The character name." + Description = "Avatar name." }, new QueryArgument> { @@ -90,16 +91,17 @@ public ActionMutation() }); Field>("hackAndSlash", + description: "Start stage for get material.", arguments: new QueryArguments( new QueryArgument> { Name = "avatarAddress", - Description = "AvatarState address." + Description = "Avatar address." }, new QueryArgument> { Name = "worldId", - Description = "World ID." + Description = "World ID containing the stage ID." }, new QueryArgument> { @@ -114,7 +116,7 @@ public ActionMutation() new QueryArgument> { Name = "rankingArenaAddress", - Description = "AvatarState rankingMapAddress." + Description = "Address of RankingMapState containing the avatar address." }, new QueryArgument> { @@ -173,26 +175,27 @@ public ActionMutation() }); Field>("combinationEquipment", + description: "Combination new equipment.", arguments: new QueryArguments( new QueryArgument> { Name = "avatarAddress", - Description = "AvatarState address." + Description = "Avatar address to create equipment." }, new QueryArgument> { Name = "recipeId", - Description = "EquipmentRecipe ID." + Description = "EquipmentRecipe ID from EquipmentRecipeSheet." }, new QueryArgument> { Name = "slotIndex", - Description = "The index of combination slot. 0 ~ 3" + Description = "The index of empty combination slot for combination equipment. 0 ~ 3" }, new QueryArgument { Name = "subRecipeId", - Description = "EquipmentSubRecipe ID." + Description = "EquipmentSubRecipe ID from EquipmentSubRecipeSheet." } ), resolve: context => @@ -228,26 +231,27 @@ public ActionMutation() }); Field>("itemEnhancement", + description: "Upgrade equipment.", arguments: new QueryArguments( new QueryArgument> { Name = "avatarAddress", - Description = "AvatarState address." + Description = "Avatar address to upgrade equipment." }, new QueryArgument> { Name = "itemId", - Description = "Equipment Guid." + Description = "Equipment Guid for upgrade." }, new QueryArgument> { Name = "materialId", - Description = "Material Guid." + Description = "Equipment Guid for upgrade material." }, new QueryArgument> { Name = "slotIndex", - Description = "The index of combination slot. 0 ~ 3" + Description = "The index of empty combination slot for upgrade equipment. 0 ~ 3" } ), resolve: context => @@ -284,26 +288,27 @@ public ActionMutation() }); Field>("buy", + description: "Buy registered shop item.", arguments: new QueryArguments( new QueryArgument> { Name = "sellerAgentAddress", - Description = "ShopItem SellerAgentAddress." + Description = "Agent address from Registered ShopItem." }, new QueryArgument> { Name = "sellerAvatarAddress", - Description = "ShopItem SellerAvatarAddress." + Description = "Avatar address from Registered ShopItem." }, new QueryArgument> { Name = "buyerAvatarAddress", - Description = "AvatarState address." + Description = "Avatar address." }, new QueryArgument> { Name = "productId", - Description = "ShopItem Guid." + Description = "ShopItem product ID." }), resolve: context => { @@ -338,21 +343,22 @@ public ActionMutation() } }); Field>("sell", + description: "Register item to the shop.", arguments: new QueryArguments( new QueryArgument> { Name = "sellerAvatarAddress", - Description = "AvatarState address." + Description = "Avatar address for register shop item." }, new QueryArgument> { Name = "itemId", - Description = "Item Guid." + Description = "Item Guid to be registered shop." }, new QueryArgument> { Name = "price", - Description = "Item price." + Description = "Item selling price." }), resolve: context => { @@ -388,11 +394,12 @@ public ActionMutation() }); Field>("dailyReward", + description: "Get daily reward.", arguments: new QueryArguments( new QueryArgument> { Name = "avatarAddress", - Description = "AvatarState address." + Description = "Avatar address to receive reward." } ), resolve: context => @@ -423,21 +430,22 @@ public ActionMutation() }); Field>("combinationConsumable", + description: "Combination new Consumable.", arguments: new QueryArguments( new QueryArgument> { Name = "avatarAddress", - Description = "AvatarState address." + Description = "Avatar address to combination consumable." }, new QueryArgument> { Name = "recipeId", - Description = "ConsumableRecipe ID." + Description = "ConsumableRecipe ID from ConsumableRecipeSheet." }, new QueryArgument> { Name = "slotIndex", - Description = "The index of combination slot. 0 ~ 3" + Description = "The index of empty combination slot for combination consumable. 0 ~ 3" } ), resolve: context => From 29ef102842a5b58f19c19f890b11e7d7bc527ad2 Mon Sep 17 00:00:00 2001 From: moreal Date: Wed, 24 Feb 2021 14:41:29 +0900 Subject: [PATCH 12/21] feat(graphql): introduce libplanet-explorer as `chainQuery' --- .../GraphQLTestUtils.cs | 3 + .../GraphTypes/GraphQLTestBase.cs | 2 + .../GraphTypes/StandaloneQueryTest.cs | 4 +- NineChronicles.Headless/BlockChainContext.cs | 23 ++++++++ .../GraphQLBuilderExtensions.cs | 16 ++++++ NineChronicles.Headless/GraphQLService.cs | 9 ++- .../GraphQLServiceExtensions.cs | 37 ++++++++++++ .../GraphTypes/ActionMutation.cs | 1 + .../GraphTypes/ActionType.cs | 17 ------ .../GraphTypes/AddressType.cs | 56 ------------------- .../GraphTypes/AppProtocolVersionType.cs | 1 + .../GraphTypes/BlockHeaderType.cs | 1 + .../GraphTypes/ByteStringType.cs | 53 ------------------ .../GraphTypes/KeyStoreMutation.cs | 1 + .../GraphTypes/KeyStoreType.cs | 1 + .../GraphTypes/NodeStatus.cs | 1 + .../GraphTypes/PrivateKeyType.cs | 1 + .../GraphTypes/ProtectedPrivateKeyType.cs | 1 + .../GraphTypes/PublicKeyType.cs | 1 + .../GraphTypes/StandaloneMutation.cs | 1 + .../GraphTypes/StandaloneQuery.cs | 9 +++ .../GraphTypes/StandaloneSubscription.cs | 1 + .../GraphTypes/StateQuery.cs | 1 + .../GraphTypes/States/AgentStateType.cs | 1 + .../GraphTypes/States/ArenaInfoType.cs | 1 + .../GraphTypes/States/AvatarStateType.cs | 1 + .../States/Models/Item/MaterialType.cs | 1 + .../States/Models/Item/ShopItemType.cs | 1 + .../GraphTypes/States/RankingInfoType.cs | 1 + .../GraphTypes/States/RankingMapStateType.cs | 1 + .../GraphTypes/States/ShopStateType.cs | 1 + .../GraphTypes/States/WeeklyArenaStateType.cs | 1 + .../GraphTypes/TransactionType.cs | 1 + .../GraphTypes/ValidationQuery.cs | 1 + .../NineChronicles.Headless.csproj | 1 + NineChronicles.Headless/UserContextBuilder.cs | 27 +++++++++ 36 files changed, 149 insertions(+), 131 deletions(-) create mode 100644 NineChronicles.Headless/BlockChainContext.cs create mode 100644 NineChronicles.Headless/GraphQLBuilderExtensions.cs delete mode 100644 NineChronicles.Headless/GraphTypes/ActionType.cs delete mode 100644 NineChronicles.Headless/GraphTypes/AddressType.cs delete mode 100644 NineChronicles.Headless/GraphTypes/ByteStringType.cs create mode 100644 NineChronicles.Headless/UserContextBuilder.cs diff --git a/NineChronicles.Headless.Tests/GraphQLTestUtils.cs b/NineChronicles.Headless.Tests/GraphQLTestUtils.cs index 7bf0cf4b2..636bd6987 100644 --- a/NineChronicles.Headless.Tests/GraphQLTestUtils.cs +++ b/NineChronicles.Headless.Tests/GraphQLTestUtils.cs @@ -4,6 +4,7 @@ using GraphQL; using GraphQL.Types; using Microsoft.Extensions.DependencyInjection; +using NCAction = Libplanet.Action.PolymorphicAction; namespace NineChronicles.Headless.Tests { @@ -23,6 +24,8 @@ public static Task ExecuteQueryAsync( services.AddSingleton(standaloneContext); } + services.AddLibplanetExplorer(); + var serviceProvider = services.BuildServiceProvider(); return ExecuteQueryAsync( serviceProvider, diff --git a/NineChronicles.Headless.Tests/GraphTypes/GraphQLTestBase.cs b/NineChronicles.Headless.Tests/GraphTypes/GraphQLTestBase.cs index 9cbfefd5b..be925801f 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/GraphQLTestBase.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/GraphQLTestBase.cs @@ -27,6 +27,7 @@ using Microsoft.Extensions.DependencyInjection; using Nekoyume.Model.State; using Nekoyume.TableData; +using NCAction = Libplanet.Action.PolymorphicAction; namespace NineChronicles.Headless.Tests.GraphTypes { @@ -96,6 +97,7 @@ public GraphQLTestBase(ITestOutputHelper output) services.AddSingleton(StandaloneContextFx); services.AddSingleton(configuration); services.AddGraphTypes(); + services.AddLibplanetExplorer(); services.AddSingleton(); ServiceProvider serviceProvider = services.BuildServiceProvider(); Schema = new StandaloneSchema(serviceProvider); diff --git a/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.cs index 5b3ecd5e6..24b8c8924 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.cs @@ -536,7 +536,7 @@ public async Task GetTx() timestamp updatedAddresses actions {{ - plainValue + inspection }} }} }}"; @@ -576,7 +576,7 @@ public async Task GetTx() var plainValue = tx["actions"] .As>() .First() - .As>()["plainValue"]; + .As>()["inspection"]; Assert.Equal(transaction.Actions.First().PlainValue.Inspection, plainValue); } diff --git a/NineChronicles.Headless/BlockChainContext.cs b/NineChronicles.Headless/BlockChainContext.cs new file mode 100644 index 000000000..5a3cedaf0 --- /dev/null +++ b/NineChronicles.Headless/BlockChainContext.cs @@ -0,0 +1,23 @@ +using Libplanet.Action; +using Libplanet.Blockchain; +using Libplanet.Explorer.Interfaces; +using Libplanet.Store; +using Nekoyume.Action; +using NCAction = Libplanet.Action.PolymorphicAction; + +namespace NineChronicles.Headless +{ + public class BlockChainContext : IBlockChainContext + { + private readonly StandaloneContext _standaloneContext; + + public BlockChainContext(StandaloneContext standaloneContext) + { + _standaloneContext = standaloneContext; + } + + public bool Preloaded => _standaloneContext.NodeStatus.PreloadEnded; + public BlockChain> BlockChain => _standaloneContext.BlockChain; + public IStore Store => _standaloneContext.Store; + } +} diff --git a/NineChronicles.Headless/GraphQLBuilderExtensions.cs b/NineChronicles.Headless/GraphQLBuilderExtensions.cs new file mode 100644 index 000000000..07dce444c --- /dev/null +++ b/NineChronicles.Headless/GraphQLBuilderExtensions.cs @@ -0,0 +1,16 @@ +using GraphQL.Server; +using Libplanet.Action; + +namespace NineChronicles.Headless +{ + public static class GraphQLBuilderExtensions + { + public static IGraphQLBuilder AddLibplanetExplorer(this IGraphQLBuilder builder) + where T : IAction, new() + { + builder.Services.AddLibplanetExplorer(); + + return builder; + } + } +} diff --git a/NineChronicles.Headless/GraphQLService.cs b/NineChronicles.Headless/GraphQLService.cs index 2d19c0f9e..832388a1f 100644 --- a/NineChronicles.Headless/GraphQLService.cs +++ b/NineChronicles.Headless/GraphQLService.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using GraphQL.Server; using Microsoft.AspNetCore.Builder; @@ -9,6 +10,7 @@ using NineChronicles.Headless.Middleware; using NineChronicles.Headless.Properties; using Serilog; +using NCAction = Libplanet.Action.PolymorphicAction; namespace NineChronicles.Headless { @@ -91,15 +93,16 @@ public void ConfigureServices(IServiceCollection services) options.EnableMetrics = true; options.UnhandledExceptionDelegate = context => { - Log.Error( - context.Exception, - context.ErrorMessage); + Console.Error.WriteLine(context.Exception.ToString()); + Console.Error.WriteLine(context.ErrorMessage); }; }) .AddSystemTextJson() .AddWebSockets() .AddDataLoader() .AddGraphTypes(typeof(StandaloneSchema)) + .AddLibplanetExplorer() + .AddUserContextBuilder() .AddGraphQLAuthorization( options => options.AddPolicy( LocalPolicyKey, diff --git a/NineChronicles.Headless/GraphQLServiceExtensions.cs b/NineChronicles.Headless/GraphQLServiceExtensions.cs index 7b67e35d0..326135118 100644 --- a/NineChronicles.Headless/GraphQLServiceExtensions.cs +++ b/NineChronicles.Headless/GraphQLServiceExtensions.cs @@ -2,8 +2,13 @@ using System.Linq; using System.Reflection; using GraphQL.Types; +using Libplanet.Action; +using Libplanet.Explorer.GraphTypes; +using Libplanet.Explorer.Interfaces; +using Libplanet.Explorer.Queries; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using NCAction = Libplanet.Action.PolymorphicAction; namespace NineChronicles.Headless { @@ -24,5 +29,37 @@ public static IServiceCollection AddGraphTypes(this IServiceCollection services) return services; } + + public static IServiceCollection AddLibplanetScalarTypes(this IServiceCollection services) + { + services.TryAddSingleton(); + services.TryAddSingleton(); + + return services; + } + + public static IServiceCollection AddBlockChainContext(this IServiceCollection services) + { + services.TryAddSingleton, BlockChainContext>(); + + return services; + } + + public static IServiceCollection AddLibplanetExplorer(this IServiceCollection services) + where T : IAction, new() + { + services.AddLibplanetScalarTypes(); + services.AddBlockChainContext(); + + services.TryAddSingleton>(); + services.TryAddSingleton>(); + services.TryAddSingleton>(); + services.TryAddSingleton>(); + services.TryAddSingleton>(); + services.TryAddSingleton>(); + services.TryAddSingleton>(); + + return services; + } } } diff --git a/NineChronicles.Headless/GraphTypes/ActionMutation.cs b/NineChronicles.Headless/GraphTypes/ActionMutation.cs index 8ac09644f..853887260 100644 --- a/NineChronicles.Headless/GraphTypes/ActionMutation.cs +++ b/NineChronicles.Headless/GraphTypes/ActionMutation.cs @@ -11,6 +11,7 @@ using Serilog; using System; using System.Collections.Generic; +using Libplanet.Explorer.GraphTypes; using Libplanet.Tx; using NineChroniclesActionType = Libplanet.Action.PolymorphicAction; diff --git a/NineChronicles.Headless/GraphTypes/ActionType.cs b/NineChronicles.Headless/GraphTypes/ActionType.cs deleted file mode 100644 index dc2fef868..000000000 --- a/NineChronicles.Headless/GraphTypes/ActionType.cs +++ /dev/null @@ -1,17 +0,0 @@ -using GraphQL.Types; -using Libplanet.Action; - -namespace NineChronicles.Headless.GraphTypes -{ - public class ActionType : ObjectGraphType where T : IAction, new() - { - public ActionType() - { - Field>( - nameof(IAction.PlainValue), - resolve: context => context.Source.PlainValue.Inspection - ); - Name = "Action"; - } - } -} diff --git a/NineChronicles.Headless/GraphTypes/AddressType.cs b/NineChronicles.Headless/GraphTypes/AddressType.cs deleted file mode 100644 index 523615285..000000000 --- a/NineChronicles.Headless/GraphTypes/AddressType.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using GraphQL.Language.AST; -using GraphQL.Types; -using Libplanet; - -namespace NineChronicles.Headless.GraphTypes -{ - // Copied from planetarium/libplanet-explorer:59a2d3045e99100fb33d79cc7d0ee6fdc09a1eb4 - // https://git.io/JfXZA - public class AddressType : StringGraphType - { - public AddressType() - { - Name = "Address"; - } - - public override object Serialize(object value) - { - if (value is Address addr) - { - return addr.ToString(); - } - - return value; - } - - public override object ParseValue(object value) - { - switch (value) - { - case null: - return null; - case string hex: - if (hex.Substring(0, 2).ToLower().Equals("0x")) - { - hex = hex.Substring(2); - } - - return new Address(hex); - default: - throw new ArgumentException( - $"Expected a hexadecimal string but {value}", nameof(value)); - } - } - - public override object ParseLiteral(IValue value) - { - if (value is StringValue) - { - return ParseValue(value.Value); - } - - return null; - } - } -} diff --git a/NineChronicles.Headless/GraphTypes/AppProtocolVersionType.cs b/NineChronicles.Headless/GraphTypes/AppProtocolVersionType.cs index 6967e75ef..1346ea8a1 100644 --- a/NineChronicles.Headless/GraphTypes/AppProtocolVersionType.cs +++ b/NineChronicles.Headless/GraphTypes/AppProtocolVersionType.cs @@ -1,6 +1,7 @@ using Bencodex; using Libplanet.Net; using GraphQL.Types; +using Libplanet.Explorer.GraphTypes; namespace NineChronicles.Headless.GraphTypes { diff --git a/NineChronicles.Headless/GraphTypes/BlockHeaderType.cs b/NineChronicles.Headless/GraphTypes/BlockHeaderType.cs index 1d5b2a89c..bcb81c907 100644 --- a/NineChronicles.Headless/GraphTypes/BlockHeaderType.cs +++ b/NineChronicles.Headless/GraphTypes/BlockHeaderType.cs @@ -3,6 +3,7 @@ using Libplanet; using Libplanet.Action; using Libplanet.Blocks; +using Libplanet.Explorer.GraphTypes; namespace NineChronicles.Headless.GraphTypes { diff --git a/NineChronicles.Headless/GraphTypes/ByteStringType.cs b/NineChronicles.Headless/GraphTypes/ByteStringType.cs deleted file mode 100644 index 3cc32afe5..000000000 --- a/NineChronicles.Headless/GraphTypes/ByteStringType.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using GraphQL.Language.AST; -using GraphQL.Types; -using Libplanet; -using Serilog; - -namespace NineChronicles.Headless.GraphTypes -{ - // Copied from planetarium/libplanet-explorer:59a2d3045e99100fb33d79cc7d0ee6fdc09a1eb4 - // https://git.io/JfXOt - public class ByteStringType : ScalarGraphType - { - public ByteStringType() - { - Name = "ByteString"; - } - - public override object Serialize(object value) - { - // FIXME: ScalarGraphType에 동작 원리에 대한 이해가 필요합니다. - // https://graphql-dotnet.github.io/docs/getting-started/custom-scalars/ - return value switch - { - byte[] b => ByteUtil.Hex(b), - string s => s, - _ => null - }; - } - - public override object ParseValue(object value) - { - switch (value) - { - case null: - return null; - case string hex: - return ByteUtil.ParseHex(hex); - default: - throw new ArgumentException("Expected a hexadecimal string.", nameof(value)); - } - } - - public override object ParseLiteral(IValue value) - { - if (value is StringValue) - { - return ParseValue(value.Value); - } - - return null; - } - } -} diff --git a/NineChronicles.Headless/GraphTypes/KeyStoreMutation.cs b/NineChronicles.Headless/GraphTypes/KeyStoreMutation.cs index fcdcb4313..30f870f79 100644 --- a/NineChronicles.Headless/GraphTypes/KeyStoreMutation.cs +++ b/NineChronicles.Headless/GraphTypes/KeyStoreMutation.cs @@ -4,6 +4,7 @@ using GraphQL.Types; using Libplanet; using Libplanet.Crypto; +using Libplanet.Explorer.GraphTypes; using Libplanet.KeyStore; namespace NineChronicles.Headless.GraphTypes diff --git a/NineChronicles.Headless/GraphTypes/KeyStoreType.cs b/NineChronicles.Headless/GraphTypes/KeyStoreType.cs index 7b9d5366a..32eb762a5 100644 --- a/NineChronicles.Headless/GraphTypes/KeyStoreType.cs +++ b/NineChronicles.Headless/GraphTypes/KeyStoreType.cs @@ -4,6 +4,7 @@ using GraphQL.Types; using Libplanet; using Libplanet.Crypto; +using Libplanet.Explorer.GraphTypes; using Libplanet.KeyStore; using Org.BouncyCastle.Security; diff --git a/NineChronicles.Headless/GraphTypes/NodeStatus.cs b/NineChronicles.Headless/GraphTypes/NodeStatus.cs index 30d2ad65d..37facbcd9 100644 --- a/NineChronicles.Headless/GraphTypes/NodeStatus.cs +++ b/NineChronicles.Headless/GraphTypes/NodeStatus.cs @@ -8,6 +8,7 @@ using Libplanet.Action; using Libplanet.Blockchain; using Libplanet.Blocks; +using Libplanet.Explorer.GraphTypes; using Libplanet.Store; using Libplanet.Tx; using NCAction = Libplanet.Action.PolymorphicAction; diff --git a/NineChronicles.Headless/GraphTypes/PrivateKeyType.cs b/NineChronicles.Headless/GraphTypes/PrivateKeyType.cs index 9c1ac6ee3..e12ee6b1f 100644 --- a/NineChronicles.Headless/GraphTypes/PrivateKeyType.cs +++ b/NineChronicles.Headless/GraphTypes/PrivateKeyType.cs @@ -1,6 +1,7 @@ using GraphQL.Types; using Libplanet; using Libplanet.Crypto; +using Libplanet.Explorer.GraphTypes; namespace NineChronicles.Headless.GraphTypes { diff --git a/NineChronicles.Headless/GraphTypes/ProtectedPrivateKeyType.cs b/NineChronicles.Headless/GraphTypes/ProtectedPrivateKeyType.cs index 665291edc..0a737b040 100644 --- a/NineChronicles.Headless/GraphTypes/ProtectedPrivateKeyType.cs +++ b/NineChronicles.Headless/GraphTypes/ProtectedPrivateKeyType.cs @@ -1,4 +1,5 @@ using GraphQL.Types; +using Libplanet.Explorer.GraphTypes; using Libplanet.KeyStore; namespace NineChronicles.Headless.GraphTypes diff --git a/NineChronicles.Headless/GraphTypes/PublicKeyType.cs b/NineChronicles.Headless/GraphTypes/PublicKeyType.cs index 425176b84..120a8d07f 100644 --- a/NineChronicles.Headless/GraphTypes/PublicKeyType.cs +++ b/NineChronicles.Headless/GraphTypes/PublicKeyType.cs @@ -2,6 +2,7 @@ using GraphQL.Types; using Libplanet; using Libplanet.Crypto; +using Libplanet.Explorer.GraphTypes; namespace NineChronicles.Headless.GraphTypes { diff --git a/NineChronicles.Headless/GraphTypes/StandaloneMutation.cs b/NineChronicles.Headless/GraphTypes/StandaloneMutation.cs index 78101aa4f..10a72339a 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneMutation.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneMutation.cs @@ -12,6 +12,7 @@ using Serilog; using System; using GraphQL.Server.Authorization.AspNetCore; +using Libplanet.Explorer.GraphTypes; using Microsoft.Extensions.Configuration; using NCAction = Libplanet.Action.PolymorphicAction; diff --git a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs index 628572d30..8005e8c27 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs @@ -1,4 +1,5 @@ #nullable enable +using System; using System.Security.Cryptography; using Bencodex; using Bencodex.Types; @@ -8,6 +9,9 @@ using Libplanet.Action; using Libplanet.Assets; using Libplanet.Blockchain; +using Libplanet.Explorer.GraphTypes; +using Libplanet.Explorer.Interfaces; +using Libplanet.Store; using Microsoft.Extensions.Configuration; using Libplanet.Tx; using Nekoyume.Action; @@ -88,6 +92,11 @@ public StandaloneQuery(StandaloneContext standaloneContext, IConfiguration confi } ); + Field>>( + name: "chainQuery", + resolve: context => new { } + ); + Field>( name: "validation", description: "The validation method provider for Libplanet types.", diff --git a/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs b/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs index 1ce7a1a4e..eaaf173cf 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs @@ -12,6 +12,7 @@ using Lib9c.Renderer; using Libplanet; using Libplanet.Blockchain; +using Libplanet.Explorer.GraphTypes; using Libplanet.Net; using Libplanet.Headless; using Nekoyume.Action; diff --git a/NineChronicles.Headless/GraphTypes/StateQuery.cs b/NineChronicles.Headless/GraphTypes/StateQuery.cs index 11ffb67a1..f29bc2d04 100644 --- a/NineChronicles.Headless/GraphTypes/StateQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StateQuery.cs @@ -3,6 +3,7 @@ using GraphQL.Types; using Libplanet; using Libplanet.Action; +using Libplanet.Explorer.GraphTypes; using Nekoyume; using Nekoyume.Model.State; using NineChronicles.Headless.GraphTypes.States; diff --git a/NineChronicles.Headless/GraphTypes/States/AgentStateType.cs b/NineChronicles.Headless/GraphTypes/States/AgentStateType.cs index a480f81ac..1df678664 100644 --- a/NineChronicles.Headless/GraphTypes/States/AgentStateType.cs +++ b/NineChronicles.Headless/GraphTypes/States/AgentStateType.cs @@ -5,6 +5,7 @@ using Libplanet.Action; using Libplanet.Assets; using Libplanet.Blockchain; +using Libplanet.Explorer.GraphTypes; using Nekoyume.Action; using Nekoyume.Model.State; diff --git a/NineChronicles.Headless/GraphTypes/States/ArenaInfoType.cs b/NineChronicles.Headless/GraphTypes/States/ArenaInfoType.cs index 49678dde2..7fd9dcfb6 100644 --- a/NineChronicles.Headless/GraphTypes/States/ArenaInfoType.cs +++ b/NineChronicles.Headless/GraphTypes/States/ArenaInfoType.cs @@ -1,4 +1,5 @@ using GraphQL.Types; +using Libplanet.Explorer.GraphTypes; using Nekoyume.Model.State; namespace NineChronicles.Headless.GraphTypes.States diff --git a/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs b/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs index 32a3f7c67..9d7e2f41e 100644 --- a/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs +++ b/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs @@ -1,4 +1,5 @@ using GraphQL.Types; +using Libplanet.Explorer.GraphTypes; using Nekoyume.Model.State; using NineChronicles.Headless.GraphTypes.States.Models; using NineChronicles.Headless.GraphTypes.States.Models.World; diff --git a/NineChronicles.Headless/GraphTypes/States/Models/Item/MaterialType.cs b/NineChronicles.Headless/GraphTypes/States/Models/Item/MaterialType.cs index e3e958000..c197bb9eb 100644 --- a/NineChronicles.Headless/GraphTypes/States/Models/Item/MaterialType.cs +++ b/NineChronicles.Headless/GraphTypes/States/Models/Item/MaterialType.cs @@ -1,4 +1,5 @@ using GraphQL.Types; +using Libplanet.Explorer.GraphTypes; using Nekoyume.Model.Item; namespace NineChronicles.Headless.GraphTypes.States.Models.Item diff --git a/NineChronicles.Headless/GraphTypes/States/Models/Item/ShopItemType.cs b/NineChronicles.Headless/GraphTypes/States/Models/Item/ShopItemType.cs index 9273ccaf5..ca5f7fe35 100644 --- a/NineChronicles.Headless/GraphTypes/States/Models/Item/ShopItemType.cs +++ b/NineChronicles.Headless/GraphTypes/States/Models/Item/ShopItemType.cs @@ -1,4 +1,5 @@ using GraphQL.Types; +using Libplanet.Explorer.GraphTypes; using Nekoyume.Model.Item; namespace NineChronicles.Headless.GraphTypes.States.Models.Item diff --git a/NineChronicles.Headless/GraphTypes/States/RankingInfoType.cs b/NineChronicles.Headless/GraphTypes/States/RankingInfoType.cs index 8f46a3fc6..17a8269bf 100644 --- a/NineChronicles.Headless/GraphTypes/States/RankingInfoType.cs +++ b/NineChronicles.Headless/GraphTypes/States/RankingInfoType.cs @@ -1,4 +1,5 @@ using GraphQL.Types; +using Libplanet.Explorer.GraphTypes; using Nekoyume.Model.State; namespace NineChronicles.Headless.GraphTypes.States diff --git a/NineChronicles.Headless/GraphTypes/States/RankingMapStateType.cs b/NineChronicles.Headless/GraphTypes/States/RankingMapStateType.cs index d92b64398..219e6f139 100644 --- a/NineChronicles.Headless/GraphTypes/States/RankingMapStateType.cs +++ b/NineChronicles.Headless/GraphTypes/States/RankingMapStateType.cs @@ -1,4 +1,5 @@ using GraphQL.Types; +using Libplanet.Explorer.GraphTypes; using Nekoyume.Action; using Nekoyume.Model.State; diff --git a/NineChronicles.Headless/GraphTypes/States/ShopStateType.cs b/NineChronicles.Headless/GraphTypes/States/ShopStateType.cs index 83b84e459..74149bc19 100644 --- a/NineChronicles.Headless/GraphTypes/States/ShopStateType.cs +++ b/NineChronicles.Headless/GraphTypes/States/ShopStateType.cs @@ -2,6 +2,7 @@ using System.Linq; using GraphQL; using GraphQL.Types; +using Libplanet.Explorer.GraphTypes; using Nekoyume.Model.Item; using Nekoyume.Model.State; using NineChronicles.Headless.GraphTypes.States.Models.Item; diff --git a/NineChronicles.Headless/GraphTypes/States/WeeklyArenaStateType.cs b/NineChronicles.Headless/GraphTypes/States/WeeklyArenaStateType.cs index 81eb4325f..5c10ff290 100644 --- a/NineChronicles.Headless/GraphTypes/States/WeeklyArenaStateType.cs +++ b/NineChronicles.Headless/GraphTypes/States/WeeklyArenaStateType.cs @@ -1,4 +1,5 @@ using GraphQL.Types; +using Libplanet.Explorer.GraphTypes; using Nekoyume.Model.State; namespace NineChronicles.Headless.GraphTypes.States diff --git a/NineChronicles.Headless/GraphTypes/TransactionType.cs b/NineChronicles.Headless/GraphTypes/TransactionType.cs index 9d5df9589..e5ed5afbe 100644 --- a/NineChronicles.Headless/GraphTypes/TransactionType.cs +++ b/NineChronicles.Headless/GraphTypes/TransactionType.cs @@ -1,5 +1,6 @@ using GraphQL.Types; using Libplanet.Action; +using Libplanet.Explorer.GraphTypes; using Libplanet.Tx; namespace NineChronicles.Headless.GraphTypes diff --git a/NineChronicles.Headless/GraphTypes/ValidationQuery.cs b/NineChronicles.Headless/GraphTypes/ValidationQuery.cs index c3ff9be21..a951b3ff2 100644 --- a/NineChronicles.Headless/GraphTypes/ValidationQuery.cs +++ b/NineChronicles.Headless/GraphTypes/ValidationQuery.cs @@ -4,6 +4,7 @@ using GraphQL; using GraphQL.Types; using Libplanet.Crypto; +using Libplanet.Explorer.GraphTypes; using Serilog; namespace NineChronicles.Headless.GraphTypes diff --git a/NineChronicles.Headless/NineChronicles.Headless.csproj b/NineChronicles.Headless/NineChronicles.Headless.csproj index 5f5f05068..ee269c564 100644 --- a/NineChronicles.Headless/NineChronicles.Headless.csproj +++ b/NineChronicles.Headless/NineChronicles.Headless.csproj @@ -17,6 +17,7 @@ + diff --git a/NineChronicles.Headless/UserContextBuilder.cs b/NineChronicles.Headless/UserContextBuilder.cs new file mode 100644 index 000000000..3ade5a130 --- /dev/null +++ b/NineChronicles.Headless/UserContextBuilder.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using GraphQL.Server.Transports.AspNetCore; +using Libplanet.Explorer.Interfaces; +using Microsoft.AspNetCore.Http; +using NCAction = Libplanet.Action.PolymorphicAction; + +namespace NineChronicles.Headless +{ + public class UserContextBuilder : IUserContextBuilder + { + private readonly StandaloneContext _standaloneContext; + + public UserContextBuilder(StandaloneContext standaloneContext) + { + _standaloneContext = standaloneContext; + } + + public Task> BuildUserContext(HttpContext httpContext) + { + return new ValueTask>(new Dictionary + { + [nameof(IBlockChainContext.Store)] = _standaloneContext.Store, + }).AsTask(); + } + } +} From 2d088a4fec2467b30fe5044066d6735cbf19a8f4 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Thu, 25 Feb 2021 13:59:31 +0900 Subject: [PATCH 13/21] Apply suggestions from code review Co-authored-by: Kidon (Don) Seo Co-authored-by: Lee Dogeon --- .../GraphTypes/ActionMutation.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/NineChronicles.Headless/GraphTypes/ActionMutation.cs b/NineChronicles.Headless/GraphTypes/ActionMutation.cs index 9a558c134..6672f9198 100644 --- a/NineChronicles.Headless/GraphTypes/ActionMutation.cs +++ b/NineChronicles.Headless/GraphTypes/ActionMutation.cs @@ -91,7 +91,7 @@ public ActionMutation() }); Field>("hackAndSlash", - description: "Start stage for get material.", + description: "Start stage to get material.", arguments: new QueryArguments( new QueryArgument> { @@ -175,7 +175,7 @@ public ActionMutation() }); Field>("combinationEquipment", - description: "Combination new equipment.", + description: "Combine new equipment.", arguments: new QueryArguments( new QueryArgument> { @@ -190,7 +190,7 @@ public ActionMutation() new QueryArgument> { Name = "slotIndex", - Description = "The index of empty combination slot for combination equipment. 0 ~ 3" + Description = "The empty combination slot index to combine equipment. 0 ~ 3" }, new QueryArgument { @@ -246,12 +246,12 @@ public ActionMutation() new QueryArgument> { Name = "materialId", - Description = "Equipment Guid for upgrade material." + Description = "Material Guid for equipment upgrade." }, new QueryArgument> { Name = "slotIndex", - Description = "The index of empty combination slot for upgrade equipment. 0 ~ 3" + Description = "The empty combination slot index to upgrade equipment. 0 ~ 3" } ), resolve: context => @@ -293,12 +293,12 @@ public ActionMutation() new QueryArgument> { Name = "sellerAgentAddress", - Description = "Agent address from Registered ShopItem." + Description = "Agent address from registered ShopItem." }, new QueryArgument> { Name = "sellerAvatarAddress", - Description = "Avatar address from Registered ShopItem." + Description = "Avatar address from registered ShopItem." }, new QueryArgument> { @@ -348,12 +348,12 @@ public ActionMutation() new QueryArgument> { Name = "sellerAvatarAddress", - Description = "Avatar address for register shop item." + Description = "Avatar address to register shop item." }, new QueryArgument> { Name = "itemId", - Description = "Item Guid to be registered shop." + Description = "Item Guid to register on shop." }, new QueryArgument> { @@ -430,12 +430,12 @@ public ActionMutation() }); Field>("combinationConsumable", - description: "Combination new Consumable.", + description: "Combine new Consumable.", arguments: new QueryArguments( new QueryArgument> { Name = "avatarAddress", - Description = "Avatar address to combination consumable." + Description = "Avatar address to combine consumable." }, new QueryArgument> { @@ -445,7 +445,7 @@ public ActionMutation() new QueryArgument> { Name = "slotIndex", - Description = "The index of empty combination slot for combination consumable. 0 ~ 3" + Description = "The empty combination slot index to combine consumable. 0 ~ 3" } ), resolve: context => From 6f03e7fea5609573d880698db227798b81433dd7 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Fri, 26 Feb 2021 16:30:27 +0900 Subject: [PATCH 14/21] Add schema --- schema.graphql | 830 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 830 insertions(+) create mode 100644 schema.graphql diff --git a/schema.graphql b/schema.graphql new file mode 100644 index 000000000..af0bd3668 --- /dev/null +++ b/schema.graphql @@ -0,0 +1,830 @@ +schema { + query: StandaloneQuery + mutation: StandaloneMutation + subscription: StandaloneSubscription +} + +type StandaloneQuery { + # Check if the provided address is activated. + activationStatus: ActivationStatusQuery! + getTx( + # transaction id. + txId: TxId! + ): TransactionType_1 + goldBalance( + # Target address to query + address: Address! + + # Offset block hash for query. + hash: ByteString + ): String! + keyStore: KeyStoreType + nextTxNonce( + # Target address to query + address: Address! + ): Long! + nodeStatus: NodeStatusType! + + # Get the peer's block chain state + peerChainState: PeerChainStateQuery! + state( + # The address of state to fetch from the chain. + address: Address! + + # The hash of the block used to fetch state from chain. + hash: ByteString + ): ByteString + stateQuery( + # Offset block hash for query. + hash: ByteString + ): StateQuery! + + # The validation method provider for Libplanet types. + validation: ValidationQuery! +} + +# An delegate to provide read-only view of account states. +# Gets an account state of the given +# .If the given has never been set +# its account status, returns null instead of throwing +# any exception. +type StateQuery { + # State for agent. + agent( + # Address of agent. + address: Address! + ): AgentStateType + + # State for avatar. + avatar( + # Address of avatar. + address: Address + ): AvatarStateType + + # State for avatar EXP record. + rankingMap( + # RankingMapState index. 0 ~ 99 + index: Int! + ): RankingMapStateType + + # State for shop. + shop: ShopStateType + + # State for weekly arena. + weeklyArena( + # WeeklyArenaState index. It increases every 56,000 blocks. + index: Int! + ): WeeklyArenaStateType +} + +type AvatarStateType { + # Current ActionPoint. + actionPoint: Int! + + # Address of avatar. + address: Address! + + # Address of agent. + agentAddress: Address! + + # Block index at the latest executed action. + blockIndex: Int! + + # Character ID from CharacterSheet. + characterId: Int! + + # Address list of combination slot. + combinationSlotAddresses: [Address!]! + + # Block index at the DailyReward execution. + dailyRewardReceivedIndex: Long! + + # Index of ear color. + ear: Int! + + # List of quest event ID. + eventMap: CollectionMapType! + + # Avatar total EXP. + exp: Int! + + # Index of hair color. + hair: Int! + + # Avatar inventory. + inventory: InventoryType! + + # List of acquired item ID. + itemMap: CollectionMapType! + + # Index of eye color. + lens: Int! + + # Avatar Level. + level: Int! + + # List of mail. + mailBox: MailBoxType! + + # List of defeated monster ID. + monsterMap: CollectionMapType! + + # Avatar name. + name: String! + + # List of quest. + questList: QuestListType! + + # Address of the RankingMapState where this avatar information is recorded. + rankingMapAddress: Address! + + # List of cleared stage ID. + stageMap: CollectionMapType! + + # Index of tail color. + tail: Int! + + # Block index at the latest executed action. + updatedAt: Long! + + # World & Stage information. + worldInformation: WorldInformationType! +} + +scalar Address + +scalar Long + +type InventoryType { + # List of Consumables. + consumables: [ConsumableType!]! + + # List of Costumes. + costumes: [CostumeType!]! + + # List of Equipments. + equipments: [EquipmentType!]! + + # List of Materials. + materials: [MaterialType!]! +} + +type ConsumableType { + # Item elemental. + elementalType: ElementalType! + + # Grade from ItemSheet. + grade: Int! + + # ID from ItemSheet. + id: Int! + itemId: Guid! + + # Item subcategory. + itemSubType: ItemSubType! + + # Item category. + itemType: ItemType! + mainStat: StatType! +} + +enum ItemType { + CONSUMABLE + COSTUME + EQUIPMENT + MATERIAL +} + +enum ItemSubType { + FOOD + FULL_COSTUME + HAIR_COSTUME + EAR_COSTUME + EYE_COSTUME + TAIL_COSTUME + WEAPON + ARMOR + BELT + NECKLACE + RING + EQUIPMENT_MATERIAL + FOOD_MATERIAL + MONSTER_PART + NORMAL_MATERIAL + HOURGLASS + AP_STONE + CHEST + TITLE +} + +enum ElementalType { + NORMAL + FIRE + WATER + LAND + WIND +} + +scalar Guid + +enum StatType { + NONE + HP + ATK + DEF + CRI + HIT + SPD +} + +type MaterialType { + # Item elemental. + elementalType: ElementalType! + + # Grade from ItemSheet. + grade: Int! + + # ID from ItemSheet. + id: Int! + itemId: ByteString! + + # Item subcategory. + itemSubType: ItemSubType! + + # Item category. + itemType: ItemType! +} + +scalar ByteString + +type EquipmentType { + # Item elemental. + elementalType: ElementalType! + equipped: Boolean! + + # Grade from ItemSheet. + grade: Int! + + # ID from ItemSheet. + id: Int! + itemId: Guid! + + # Item subcategory. + itemSubType: ItemSubType! + + # Item category. + itemType: ItemType! + setId: Int! + stat: DecimalStatType! +} + +type DecimalStatType { + type: StatType! + value: Decimal! +} + +scalar Decimal + +type CostumeType { + # Item elemental. + elementalType: ElementalType! + + # Status of Avatar equipped. + equipped: Boolean! + + # Grade from ItemSheet. + grade: Int! + + # ID from ItemSheet. + id: Int! + + # Guid of costume. + itemId: Guid! + + # Item subcategory. + itemSubType: ItemSubType! + + # Item category. + itemType: ItemType! +} + +type CollectionMapType { + count: Int! + pairs: [[Int]!]! +} + +type QuestListType { + completedQuestIds: [Int!]! +} + +type MailBoxType { + count: Int! + mails: [MailType!]! +} + +type MailType { + blockIndex: Long! + id: Guid! + requiredBlockIndex: Long! +} + +type WorldInformationType { + isStageCleared(stageId: Int!): Boolean! + isWorldUnlocked(worldId: Int!): Boolean! + world(worldId: Int!): WorldType! +} + +type WorldType { + id: Int! + isStageCleared: Boolean! + isUnlocked: Boolean! + name: String! + stageBegin: Int! + stageClearedBlockIndex: Long! + stageClearedId: Int! + stageEnd: Int! + unlockedBlockIndex: Long! +} + +type RankingMapStateType { + # Address of RankingMapState. + address: Address! + + # RankingMapState Capacity. + capacity: Int! + + # List of RankingInfo. + rankingInfos: [RankingInfoType!]! +} + +type RankingInfoType { + # Address of agent. + agentAddress: Address! + + # Equipped Armor ID from EquipmentItemSheet. + armorId: Int! + + # Address of avatar. + avatarAddress: Address! + + # Avatar name. + avatarName: String! + + # Avatar total EXP. + exp: Long! + + # Avatar Level. + level: Int! + + # Block index at Latest stage cleared. + stageClearedBlockIndex: Long! + + # Block index at RankingInfo update. + updatedAt: Long! +} + +type ShopStateType { + # Address of shop. + address: Address! + + # List of ShopItem. + products( + # Filter for item id. + id: Int + + # Filter for ItemSubType. see from https://github.com/planetarium/lib9c/blob/main/Lib9c/Model/Item/ItemType.cs#L13 + itemSubType: ItemSubType + + # Filter for item maximum price. + maximumPrice: Int + ): [ShopItemType]! +} + +type ShopItemType { + # Costume information. + costume: CostumeType + + # Equipment / Consumable information. + itemUsable: ItemUsableType + + # Item price. + price: String! + + # Guid of product registered. + productId: Guid! + + # Address of seller agent. + sellerAgentAddress: Address! + + # Address of seller avatar. + sellerAvatarAddress: Address! +} + +type ItemUsableType { + # Item elemental. + elementalType: ElementalType! + + # Grade from ItemSheet. + grade: Int! + + # ID from ItemSheet. + id: Int! + + # Guid of item. + itemId: Guid! + + # Item subcategory. + itemSubType: ItemSubType! + + # Item category. + itemType: ItemType! +} + +type WeeklyArenaStateType { + address: Address! + ended: Boolean! + orderedArenaInfos: [ArenaInfoType]! +} + +type ArenaInfoType { + active: Boolean! + agentAddress: Address! + arenaRecord: ArenaRecordType! + armorId: Int! + avatarAddress: Address! + avatarName: String! + combatPoint: Int! + dailyChallengeCount: Int! + level: Int! + score: Int! +} + +type ArenaRecordType { + draw: Int + lose: Int + win: Int +} + +type AgentStateType { + # Address of agent. + address: Address! + + # Address list of avatar. + avatarAddresses: [Address!] + + # Current NCG. + gold: String! +} + +# The interface to store s. An appropriate implementation +# should be used according to a running platform. +type KeyStoreType { + decryptedPrivateKey(address: Address!, passphrase: String!): ByteString! + + # An API to provide conversion to public-key, address. + privateKey( + # A representation of public-key with hexadecimal format. + hex: ByteString! + ): PrivateKeyType! + protectedPrivateKeys: [ProtectedPrivateKeyType!]! +} + +# Protects a with a passphrase (i.e., password). +type ProtectedPrivateKeyType { + address: Address! +} + +# A secret part of a key pair involved in +# ECDSA, the digital +# signature algorithm on which the Libplanet is based. It can be used to +# create signatures, which can be verified with the corresponding +# , as well as to decrypt +# messages which were encrypted with the corresponding +# . +# Note that it uses secp256k1 as the parameters of the elliptic curve, which is +# the same to Bitcoin and +# Ethereum. +# It means private keys generated for Bitcoin/Ethereum can be used by +# Libplanet-backed games/apps too. +type PrivateKeyType { + # A representation of private-key with hexadecimal format. + hex: ByteString! + + # A public-key derived from the private-key. + publicKey: PublicKeyType! +} + +# A public part of a key pair involved in +# ECDSA, the digital +# signature algorithm on which the Libplanet is based. +# It can be used to verify signatures created with the corresponding +# and to encrypt messages for someone +# possessing the corresponding . +# This can be distributed publicly, hence the name. +# Note that it uses secp256k1 as the parameters of the elliptic curve, which is same to +# Bitcoin and +# Ethereum. +# It means public keys generated for Bitcoin/Ethereum can be used by +# Libplanet-backed games/apps too. +type PublicKeyType { + # An address derived from the public-key. + address: Address! + + # A representation of public-key with hexadecimal format. + hex( + # A flag to determine whether to compress public-key. + compress: Boolean + ): ByteString! +} + +type NodeStatusType { + bootstrapEnded: Boolean! + genesis: BlockHeader! + + # Whether it is mining. + isMining: Boolean! + preloadEnded: Boolean! + + # Staged TxIds from the current node. + stagedTxIds( + # Target address to query + address: Address + ): [TxId] + tip: BlockHeader! + + # The topmost blocks from the current node. + topmostBlocks( + # The number of blocks to get. + limit: Int! + + # List only blocks mined by the given address. (List everything if omitted.) + miner: Address + ): [BlockHeader]! +} + +type BlockHeader { + hash: String! + id: ID! + index: Int! + miner: Address +} + +scalar TxId + +type ValidationQuery { + metadata( + # The raw value of json metadata. + raw: String! + ): Boolean! + privateKey( + # The raw value of private-key, presented as hexadecimal. + hex: ByteString! + ): Boolean! + publicKey( + # The raw value of public-key, presented as hexadecimal. + hex: ByteString! + ): Boolean! +} + +type ActivationStatusQuery { + activated: Boolean! +} + +type PeerChainStateQuery { + state: [String]! +} + +type TransactionType_1 { + actions: [Action]! + id: TxId! + nonce: Long! + publicKey: PublicKeyType! + signature: ByteString! + signer: Address! + timestamp: String! + updatedAddresses: [Address]! +} + +type Action { + plainValue: String! +} + +type StandaloneMutation { + action: ActionMutation + activationStatus: ActivationStatusMutation + keyStore: KeyStoreMutation + + # Add a new transaction to staging + stageTx( + # Hex-encoded bytes for new transaction. + payload: String! + ): Boolean! + transferGold(recipient: Address!, amount: String!): TxId +} + +# The interface to store s. An appropriate implementation +# should be used according to a running platform. +type KeyStoreMutation { + createPrivateKey(passphrase: String!, privateKey: ByteString): PrivateKeyType! + revokePrivateKey(address: Address!): ProtectedPrivateKeyType! +} + +type ActivationStatusMutation { + activateAccount(encodedActivationKey: String!): Boolean! +} + +type ActionMutation { + # Buy registered shop item. + buy( + # Agent address from registered ShopItem. + sellerAgentAddress: Address! + + # Avatar address from registered ShopItem. + sellerAvatarAddress: Address! + + # Avatar address. + buyerAvatarAddress: Address! + + # ShopItem product ID. + productId: Guid! + ): TxId! + + # Combine new Consumable. + combinationConsumable( + # Avatar address to combine consumable. + avatarAddress: Address! + + # ConsumableRecipe ID from ConsumableRecipeSheet. + recipeId: Int! + + # The empty combination slot index to combine consumable. 0 ~ 3 + slotIndex: Int! + ): TxId! + + # Combine new equipment. + combinationEquipment( + # Avatar address to create equipment. + avatarAddress: Address! + + # EquipmentRecipe ID from EquipmentRecipeSheet. + recipeId: Int! + + # The empty combination slot index to combine equipment. 0 ~ 3 + slotIndex: Int! + + # EquipmentSubRecipe ID from EquipmentSubRecipeSheet. + subRecipeId: Int + ): TxId! + + # Create new avatar. + createAvatar( + # Avatar name. + avatarName: String! + + # The index of character slot. 0 ~ 2 + avatarIndex: Int! + + # The index of character hair color. 0 ~ 8 + hairIndex: Int! + + # The index of character eye color. 0 ~ 8 + lensIndex: Int! + + # The index of character ear color. 0 ~ 8 + earIndex: Int! + + # The index of character tail color. 0 ~ 8 + tailIndex: Int! + ): TxId! + + # Get daily reward. + dailyReward( + # Avatar address to receive reward. + avatarAddress: Address! + ): TxId! + + # Start stage to get material. + hackAndSlash( + # Avatar address. + avatarAddress: Address! + + # World ID containing the stage ID. + worldId: Int! + + # Stage ID. + stageId: Int! + + # Address of this WeeklyArenaState + weeklyArenaAddress: Address! + + # Address of RankingMapState containing the avatar address. + rankingArenaAddress: Address! + + # List of costume id for equip. + costumeIds: [Guid] + + # List of equipment id for equip. + equipmentIds: [Guid] + + # List of consumable id for use. + consumableIds: [Guid] + ): TxId! + + # Upgrade equipment. + itemEnhancement( + # Avatar address to upgrade equipment. + avatarAddress: Address! + + # Equipment Guid for upgrade. + itemId: Guid! + + # Material Guid for equipment upgrade. + materialId: Guid! + + # The empty combination slot index to upgrade equipment. 0 ~ 3 + slotIndex: Int! + ): TxId! + + # Register item to the shop. + sell( + # Avatar address to register shop item. + sellerAvatarAddress: Address! + + # Item Guid to register on shop. + itemId: Guid! + + # Item selling price. + price: Int! + ): TxId! +} + +type StandaloneSubscription { + differentAppProtocolVersionEncounter: DifferentAppProtocolVersionEncounterType! + nodeException: NodeExceptionType! + nodeStatus: NodeStatusType + notification: NotificationType! + preloadProgress: PreloadStateType + tipChanged: TipChanged +} + +type TipChanged { + hash: ByteString + index: Long! +} + +type PreloadStateType { + currentPhase: Long! + extra: PreloadStateExtraType! + totalPhase: Long! +} + +type PreloadStateExtraType { + currentCount: Long! + totalCount: Long! + type: String! +} + +type DifferentAppProtocolVersionEncounterType { + localVersion: AppProtocolVersionType! + peer: String! + peerVersion: AppProtocolVersionType! +} + +# A claim of a version. +# Every peer in network shows others their information. +# As every peer can change its software by itself, this +# is theoretically arbitrary, hence a “claim.” (i.e., no authority).In order to verify who claimed a version, every +# has its which is made by its . +# method purposes to determine whether an information +# is claimed by its corresponding in fact. +type AppProtocolVersionType { + extra: ByteString + signature: ByteString! + signer: Address! + version: Int! +} + +type NotificationType { + # The message of Notification. + message: String + + # The type of Notification. + type: NotificationEnum! +} + +enum NotificationEnum { + REFILL + HAS + COMBINATION_EQUIPMENT + COMBINATION_CONSUMABLE + BUYER + SELLER +} + +type NodeExceptionType { + # The code of NodeException. + code: Int! + + # The message of NodeException. + message: String! +} From ca09f10f20319dcd8efd7f4ea500ede800d3dc5e Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Fri, 26 Feb 2021 16:31:56 +0900 Subject: [PATCH 15/21] Add deploy gh pages action --- .github/workflows/deploy_gh_pages.yml | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/deploy_gh_pages.yml diff --git a/.github/workflows/deploy_gh_pages.yml b/.github/workflows/deploy_gh_pages.yml new file mode 100644 index 000000000..7af2f53c7 --- /dev/null +++ b/.github/workflows/deploy_gh_pages.yml @@ -0,0 +1,37 @@ +name: Deploy gh pages +on: + push: + branches: + - development +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v2 + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + submodules: recursive + node-version: '14' + - name: Cache node modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + # npm cache files are stored in `~/.npm` on Linux/macOS + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + - name: Install dependencies + run: npm install -g @2fd/graphdoc + - name: Build + run: graphdoc -s schema.graphql -o doc + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./doc From 2f4528bd1da906aa4f8669f721ba739e746d9f3b Mon Sep 17 00:00:00 2001 From: moreal Date: Wed, 24 Feb 2021 19:14:22 +0900 Subject: [PATCH 16/21] test: initialize test project for headless executable --- ...hronicles.Headless.Executable.Tests.csproj | 22 +++++++++++++++++++ NineChronicles.Headless.Executable.sln | 14 ++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 NineChronicles.Headless.Executable.Tests/NineChronicles.Headless.Executable.Tests.csproj diff --git a/NineChronicles.Headless.Executable.Tests/NineChronicles.Headless.Executable.Tests.csproj b/NineChronicles.Headless.Executable.Tests/NineChronicles.Headless.Executable.Tests.csproj new file mode 100644 index 000000000..c63ac2b47 --- /dev/null +++ b/NineChronicles.Headless.Executable.Tests/NineChronicles.Headless.Executable.Tests.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp3.1 + 8 + + false + + + + + + + + + + + + + + diff --git a/NineChronicles.Headless.Executable.sln b/NineChronicles.Headless.Executable.sln index 8d8d14211..b82df2a45 100644 --- a/NineChronicles.Headless.Executable.sln +++ b/NineChronicles.Headless.Executable.sln @@ -23,6 +23,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libplanet.RocksDBStore", "L EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libplanet.Stun", "Lib9c\.Libplanet\Libplanet.Stun\Libplanet.Stun.csproj", "{3B2875B4-B6C6-4929-B885-18A922110ED2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NineChronicles.Headless.Executable.Tests", "NineChronicles.Headless.Executable.Tests\NineChronicles.Headless.Executable.Tests.csproj", "{6E38A2CF-B93F-4CD5-9CAC-DE121998FF18}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -153,6 +155,18 @@ Global {3B2875B4-B6C6-4929-B885-18A922110ED2}.Release|x64.Build.0 = Release|Any CPU {3B2875B4-B6C6-4929-B885-18A922110ED2}.Release|x86.ActiveCfg = Release|Any CPU {3B2875B4-B6C6-4929-B885-18A922110ED2}.Release|x86.Build.0 = Release|Any CPU + {6E38A2CF-B93F-4CD5-9CAC-DE121998FF18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6E38A2CF-B93F-4CD5-9CAC-DE121998FF18}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E38A2CF-B93F-4CD5-9CAC-DE121998FF18}.Debug|x64.ActiveCfg = Debug|Any CPU + {6E38A2CF-B93F-4CD5-9CAC-DE121998FF18}.Debug|x64.Build.0 = Debug|Any CPU + {6E38A2CF-B93F-4CD5-9CAC-DE121998FF18}.Debug|x86.ActiveCfg = Debug|Any CPU + {6E38A2CF-B93F-4CD5-9CAC-DE121998FF18}.Debug|x86.Build.0 = Debug|Any CPU + {6E38A2CF-B93F-4CD5-9CAC-DE121998FF18}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6E38A2CF-B93F-4CD5-9CAC-DE121998FF18}.Release|Any CPU.Build.0 = Release|Any CPU + {6E38A2CF-B93F-4CD5-9CAC-DE121998FF18}.Release|x64.ActiveCfg = Release|Any CPU + {6E38A2CF-B93F-4CD5-9CAC-DE121998FF18}.Release|x64.Build.0 = Release|Any CPU + {6E38A2CF-B93F-4CD5-9CAC-DE121998FF18}.Release|x86.ActiveCfg = Release|Any CPU + {6E38A2CF-B93F-4CD5-9CAC-DE121998FF18}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From cba682363f3d5b2a7c1cfa5588545386b0111ce4 Mon Sep 17 00:00:00 2001 From: moreal Date: Wed, 24 Feb 2021 19:20:33 +0900 Subject: [PATCH 17/21] feat(cli): implement `validation private-key' --- .../Commands/ValidationCommandTest.cs | 27 +++++++++++++++++++ .../Commands/ValidationCommand.cs | 27 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 NineChronicles.Headless.Executable.Tests/Commands/ValidationCommandTest.cs create mode 100644 NineChronicles.Headless.Executable/Commands/ValidationCommand.cs diff --git a/NineChronicles.Headless.Executable.Tests/Commands/ValidationCommandTest.cs b/NineChronicles.Headless.Executable.Tests/Commands/ValidationCommandTest.cs new file mode 100644 index 000000000..969dd42e7 --- /dev/null +++ b/NineChronicles.Headless.Executable.Tests/Commands/ValidationCommandTest.cs @@ -0,0 +1,27 @@ +using NineChronicles.Headless.Executable.Commands; +using Xunit; + +namespace NineChronicles.Headless.Executable.Tests.Commands +{ + public class ValidationCommandTest + { + private readonly ValidationCommand _command; + + public ValidationCommandTest() + { + _command = new ValidationCommand(); + } + + [Theory] + [InlineData("", -1)] + [InlineData("invalid hexadecimal", -1)] + [InlineData("0000000000000000000000000000000000000000000000000000000000000000", -1)] + [InlineData("ab8d591ccdcce263c39eb1f353e44b64869f0afea2df643bf6839ebde650d244", 0)] + [InlineData("d6c3e0d525dac340a132ae05aaa9f3e278d61b70d2b71326570e64aee249e566", 0)] + [InlineData("761f68d68426549df5904395b5ca5bce64a3da759085d8565242db42a5a1b0b9", 0)] + public void PrivateKey(string privateKeyHex, int exitCode) + { + Assert.Equal(exitCode, _command.PrivateKey(privateKeyHex)); + } + } +} diff --git a/NineChronicles.Headless.Executable/Commands/ValidationCommand.cs b/NineChronicles.Headless.Executable/Commands/ValidationCommand.cs new file mode 100644 index 000000000..8826db500 --- /dev/null +++ b/NineChronicles.Headless.Executable/Commands/ValidationCommand.cs @@ -0,0 +1,27 @@ +using Cocona; +using Libplanet; +using Libplanet.Crypto; + +namespace NineChronicles.Headless.Executable.Commands +{ + public class ValidationCommand : CoconaLiteConsoleAppBase + { + [Command(Description = "Validate private key")] + public int PrivateKey( + [Argument( + Name = "PRIVATE-KEY", + Description = "A hexadecimal representation of private key to validate.")] + string privateKeyHex) + { + try + { + _ = new PrivateKey(ByteUtil.ParseHex(privateKeyHex)); + return 0; + } + catch + { + return -1; + } + } + } +} From 0511471a9a8033fe583b640e83e66a1f3aa13eac Mon Sep 17 00:00:00 2001 From: moreal Date: Thu, 25 Feb 2021 11:02:32 +0900 Subject: [PATCH 18/21] feat(cli): treat `Program.Run' as primary command --- NineChronicles.Headless.Executable/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NineChronicles.Headless.Executable/Program.cs b/NineChronicles.Headless.Executable/Program.cs index e2a4917c6..49c75eaca 100644 --- a/NineChronicles.Headless.Executable/Program.cs +++ b/NineChronicles.Headless.Executable/Program.cs @@ -47,7 +47,7 @@ static void ConfigureSentryOptions(SentryOptions o) #endif } - [Command(Description = "Run headless application with options.")] + [PrimaryCommand] public async Task Run( bool noMiner = false, [Option("app-protocol-version", new[] { 'V' }, Description = "App protocol version token")] From 0c411ab5b4e38838a9953dfab4ae7a9ed6d00267 Mon Sep 17 00:00:00 2001 From: moreal Date: Thu, 25 Feb 2021 11:07:33 +0900 Subject: [PATCH 19/21] feat(cli): register `validation' subcommand --- NineChronicles.Headless.Executable/Program.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NineChronicles.Headless.Executable/Program.cs b/NineChronicles.Headless.Executable/Program.cs index 49c75eaca..4dc799d06 100644 --- a/NineChronicles.Headless.Executable/Program.cs +++ b/NineChronicles.Headless.Executable/Program.cs @@ -12,6 +12,7 @@ using Libplanet; using Libplanet.KeyStore; using Microsoft.Extensions.Hosting; +using NineChronicles.Headless.Executable.Commands; using NineChronicles.Headless.Properties; using Org.BouncyCastle.Security; using Sentry; @@ -21,6 +22,7 @@ namespace NineChronicles.Headless.Executable { + [HasSubCommands(typeof(ValidationCommand), "validation")] public class Program : CoconaLiteConsoleAppBase { const string SentryDsn = "https://ceac97d4a7d34e7b95e4c445b9b5669e@o195672.ingest.sentry.io/5287621"; From 78c629d81612a8b4b2dc3acd6c1bbf9f45264bc4 Mon Sep 17 00:00:00 2001 From: moreal Date: Thu, 25 Feb 2021 11:51:47 +0900 Subject: [PATCH 20/21] feat(cli): implement `validation public-key' --- .../Commands/ValidationCommandTest.cs | 12 ++++++++++++ .../Commands/ValidationCommand.cs | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/NineChronicles.Headless.Executable.Tests/Commands/ValidationCommandTest.cs b/NineChronicles.Headless.Executable.Tests/Commands/ValidationCommandTest.cs index 969dd42e7..0929b77bf 100644 --- a/NineChronicles.Headless.Executable.Tests/Commands/ValidationCommandTest.cs +++ b/NineChronicles.Headless.Executable.Tests/Commands/ValidationCommandTest.cs @@ -23,5 +23,17 @@ public void PrivateKey(string privateKeyHex, int exitCode) { Assert.Equal(exitCode, _command.PrivateKey(privateKeyHex)); } + + [Theory] + [InlineData("", -1)] + [InlineData("invalid hexadecimal", -1)] + [InlineData("000000000000000000000000000000000000000000000000000000000000000000", -1)] + [InlineData("03b0868d9301b30c512d307ea67af4c8bef637ef099e39d32b808a43e6b41469c5", 0)] + [InlineData("03308c1618a75e85a5fb57f7e453a642c307dc6310e90a7418b1aec565d963534a", 0)] + [InlineData("028a6190bf643175b20e4a2d1d86fe6c4b8f7d5fe3d163632be4e59f83335824b8", 0)] + public void PublicKey(string publicKeyHex, int exitCode) + { + Assert.Equal(exitCode, _command.PublicKey(publicKeyHex)); + } } } diff --git a/NineChronicles.Headless.Executable/Commands/ValidationCommand.cs b/NineChronicles.Headless.Executable/Commands/ValidationCommand.cs index 8826db500..d6eb43dc8 100644 --- a/NineChronicles.Headless.Executable/Commands/ValidationCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/ValidationCommand.cs @@ -23,5 +23,23 @@ public int PrivateKey( return -1; } } + + [Command(Description = "Validate public key")] + public int PublicKey( + [Argument( + Name = "PUBLIC-KEY", + Description = "A hexadecimal representation of public key to validate.")] + string publicKeyHex) + { + try + { + _ = new PublicKey(ByteUtil.ParseHex(publicKeyHex)); + return 0; + } + catch + { + return -1; + } + } } } From d289922d73da5c7403f6291470585a23f3d56cb8 Mon Sep 17 00:00:00 2001 From: moreal Date: Thu, 25 Feb 2021 17:57:58 +0900 Subject: [PATCH 21/21] feat(cli): print message into stdout if failed --- .../Commands/ValidationCommandTest.cs | 35 +++++++++++-------- .../IO/StringIOConsole.cs | 32 +++++++++++++++++ .../Commands/ValidationCommand.cs | 10 ++++++ .../IO/IConsole.cs | 11 ++++++ .../IO/StandardConsole.cs | 21 +++++++++++ NineChronicles.Headless.Executable/Program.cs | 5 ++- 6 files changed, 98 insertions(+), 16 deletions(-) create mode 100644 NineChronicles.Headless.Executable.Tests/IO/StringIOConsole.cs create mode 100644 NineChronicles.Headless.Executable/IO/IConsole.cs create mode 100644 NineChronicles.Headless.Executable/IO/StandardConsole.cs diff --git a/NineChronicles.Headless.Executable.Tests/Commands/ValidationCommandTest.cs b/NineChronicles.Headless.Executable.Tests/Commands/ValidationCommandTest.cs index 0929b77bf..f0ea003db 100644 --- a/NineChronicles.Headless.Executable.Tests/Commands/ValidationCommandTest.cs +++ b/NineChronicles.Headless.Executable.Tests/Commands/ValidationCommandTest.cs @@ -1,39 +1,44 @@ using NineChronicles.Headless.Executable.Commands; +using NineChronicles.Headless.Executable.Tests.IO; using Xunit; namespace NineChronicles.Headless.Executable.Tests.Commands { public class ValidationCommandTest { + private readonly StringIOConsole _console; private readonly ValidationCommand _command; public ValidationCommandTest() { - _command = new ValidationCommand(); + _console = new StringIOConsole(); + _command = new ValidationCommand(_console); } [Theory] - [InlineData("", -1)] - [InlineData("invalid hexadecimal", -1)] - [InlineData("0000000000000000000000000000000000000000000000000000000000000000", -1)] - [InlineData("ab8d591ccdcce263c39eb1f353e44b64869f0afea2df643bf6839ebde650d244", 0)] - [InlineData("d6c3e0d525dac340a132ae05aaa9f3e278d61b70d2b71326570e64aee249e566", 0)] - [InlineData("761f68d68426549df5904395b5ca5bce64a3da759085d8565242db42a5a1b0b9", 0)] - public void PrivateKey(string privateKeyHex, int exitCode) + [InlineData("", -1, "The given private key, '', had an issue during parsing.\n")] + [InlineData("invalid hexadecimal", -1, "The given private key, 'invalid hexadecimal', had an issue during parsing.\n")] + [InlineData("000000000000000000000000000000000000000000000000000000000000000000", -1, "The given private key, '000000000000000000000000000000000000000000000000000000000000000000', had an issue during parsing.\n")] + [InlineData("ab8d591ccdcce263c39eb1f353e44b64869f0afea2df643bf6839ebde650d244", 0, "")] + [InlineData("d6c3e0d525dac340a132ae05aaa9f3e278d61b70d2b71326570e64aee249e566", 0, "")] + [InlineData("761f68d68426549df5904395b5ca5bce64a3da759085d8565242db42a5a1b0b9", 0, "")] + public void PrivateKey(string privateKeyHex, int exitCode, string errorOutput) { Assert.Equal(exitCode, _command.PrivateKey(privateKeyHex)); + Assert.Equal(errorOutput, _console.Error.ToString()); } [Theory] - [InlineData("", -1)] - [InlineData("invalid hexadecimal", -1)] - [InlineData("000000000000000000000000000000000000000000000000000000000000000000", -1)] - [InlineData("03b0868d9301b30c512d307ea67af4c8bef637ef099e39d32b808a43e6b41469c5", 0)] - [InlineData("03308c1618a75e85a5fb57f7e453a642c307dc6310e90a7418b1aec565d963534a", 0)] - [InlineData("028a6190bf643175b20e4a2d1d86fe6c4b8f7d5fe3d163632be4e59f83335824b8", 0)] - public void PublicKey(string publicKeyHex, int exitCode) + [InlineData("", -1, "The given public key, '', had an issue during parsing.\n")] + [InlineData("invalid hexadecimal", -1, "The given public key, 'invalid hexadecimal', had an issue during parsing.\n")] + [InlineData("000000000000000000000000000000000000000000000000000000000000000000", -1, "The given public key, '000000000000000000000000000000000000000000000000000000000000000000', had an issue during parsing.\n")] + [InlineData("03b0868d9301b30c512d307ea67af4c8bef637ef099e39d32b808a43e6b41469c5", 0, "")] + [InlineData("03308c1618a75e85a5fb57f7e453a642c307dc6310e90a7418b1aec565d963534a", 0, "")] + [InlineData("028a6190bf643175b20e4a2d1d86fe6c4b8f7d5fe3d163632be4e59f83335824b8", 0, "")] + public void PublicKey(string publicKeyHex, int exitCode, string errorOutput) { Assert.Equal(exitCode, _command.PublicKey(publicKeyHex)); + Assert.Equal(errorOutput, _console.Error.ToString()); } } } diff --git a/NineChronicles.Headless.Executable.Tests/IO/StringIOConsole.cs b/NineChronicles.Headless.Executable.Tests/IO/StringIOConsole.cs new file mode 100644 index 000000000..e3815a0ed --- /dev/null +++ b/NineChronicles.Headless.Executable.Tests/IO/StringIOConsole.cs @@ -0,0 +1,32 @@ +using System.IO; +using NineChronicles.Headless.Executable.IO; + +namespace NineChronicles.Headless.Executable.Tests.IO +{ + public sealed class StringIOConsole : IConsole + { + public StringIOConsole(StringReader @in, StringWriter @out, StringWriter error) + { + In = @in; + Out = @out; + Error = error; + } + + public StringIOConsole(string input = "") + : this(new StringReader(input), new StringWriter(), new StringWriter()) + { + } + + public StringReader In { get; } + + public StringWriter Out { get; } + + public StringWriter Error { get; } + + TextReader IConsole.In => In; + + TextWriter IConsole.Out => Out; + + TextWriter IConsole.Error => Error; + } +} diff --git a/NineChronicles.Headless.Executable/Commands/ValidationCommand.cs b/NineChronicles.Headless.Executable/Commands/ValidationCommand.cs index d6eb43dc8..b35aa6ab5 100644 --- a/NineChronicles.Headless.Executable/Commands/ValidationCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/ValidationCommand.cs @@ -1,11 +1,19 @@ using Cocona; using Libplanet; using Libplanet.Crypto; +using NineChronicles.Headless.Executable.IO; namespace NineChronicles.Headless.Executable.Commands { public class ValidationCommand : CoconaLiteConsoleAppBase { + private readonly IConsole _console; + + public ValidationCommand(IConsole console) + { + _console = console; + } + [Command(Description = "Validate private key")] public int PrivateKey( [Argument( @@ -20,6 +28,7 @@ public int PrivateKey( } catch { + _console.Error.WriteLine($"The given private key, '{privateKeyHex}', had an issue during parsing."); return -1; } } @@ -38,6 +47,7 @@ public int PublicKey( } catch { + _console.Error.WriteLine($"The given public key, '{publicKeyHex}', had an issue during parsing."); return -1; } } diff --git a/NineChronicles.Headless.Executable/IO/IConsole.cs b/NineChronicles.Headless.Executable/IO/IConsole.cs new file mode 100644 index 000000000..cba29259c --- /dev/null +++ b/NineChronicles.Headless.Executable/IO/IConsole.cs @@ -0,0 +1,11 @@ +using System.IO; + +namespace NineChronicles.Headless.Executable.IO +{ + public interface IConsole + { + TextReader In { get; } + TextWriter Out { get; } + TextWriter Error { get; } + } +} diff --git a/NineChronicles.Headless.Executable/IO/StandardConsole.cs b/NineChronicles.Headless.Executable/IO/StandardConsole.cs new file mode 100644 index 000000000..7b399acc4 --- /dev/null +++ b/NineChronicles.Headless.Executable/IO/StandardConsole.cs @@ -0,0 +1,21 @@ +using System; +using System.IO; + +namespace NineChronicles.Headless.Executable.IO +{ + public class StandardConsole : IConsole + { + public StandardConsole() + { + In = Console.In; + Out = Console.Out; + Error = Console.Error; + } + + public TextReader In { get; } + + public TextWriter Out { get; } + + public TextWriter Error { get; } + } +} diff --git a/NineChronicles.Headless.Executable/Program.cs b/NineChronicles.Headless.Executable/Program.cs index 4dc799d06..bcae40437 100644 --- a/NineChronicles.Headless.Executable/Program.cs +++ b/NineChronicles.Headless.Executable/Program.cs @@ -13,6 +13,7 @@ using Libplanet.KeyStore; using Microsoft.Extensions.Hosting; using NineChronicles.Headless.Executable.Commands; +using NineChronicles.Headless.Executable.IO; using NineChronicles.Headless.Properties; using Org.BouncyCastle.Security; using Sentry; @@ -35,7 +36,9 @@ static async Task Main(string[] args) #if SENTRY || ! DEBUG using var _ = SentrySdk.Init(ConfigureSentryOptions); #endif - await CoconaLiteApp.RunAsync(args); + await CoconaLiteApp.Create() + .ConfigureServices(services => services.AddSingleton()) + .RunAsync(args); } static void ConfigureSentryOptions(SentryOptions o)