From fd0c71a93d1e3326dfacf26411c7de8a419b7a09 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Wed, 7 Jun 2023 21:30:19 +0900 Subject: [PATCH 01/89] Bump lib9c:2023q2-iap --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 5b2d02931..8a427fc81 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 5b2d02931ccc6d956eaad7c948db804e8656da3f +Subproject commit 8a427fc81e3b3e61c88d1faa53719ee7ddb9f8b0 From 1b2c003be943013f13738496a20999c93054e8ba Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Wed, 7 Jun 2023 21:30:58 +0900 Subject: [PATCH 02/89] Introduce HashDigestInputType --- .../GraphTypes/HashDigestInputType.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 NineChronicles.Headless/GraphTypes/HashDigestInputType.cs diff --git a/NineChronicles.Headless/GraphTypes/HashDigestInputType.cs b/NineChronicles.Headless/GraphTypes/HashDigestInputType.cs new file mode 100644 index 000000000..92ee70a57 --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/HashDigestInputType.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Security.Cryptography; +using GraphQL.Types; +using Libplanet; + +namespace NineChronicles.Headless.GraphTypes +{ + // FIXME: To StringGraphType + public class HashDigestInputType : InputObjectGraphType> + where T : HashAlgorithm + { + public HashDigestInputType() + { + Name = $"HashDigestInput_{typeof(T).Name}"; + Field( + name: "value", + description: "Hash digest hex string"); + } + + public override object ParseDictionary(IDictionary value) + { + var hashValue = (string)value["value"]!; + return HashDigest.FromString(hashValue); + } + } +} From 01bca7e8ae40d30beeb82e9804fe8037e0d5f6b7 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Wed, 7 Jun 2023 21:31:21 +0900 Subject: [PATCH 03/89] Introduce FungibleIdAndCountInputType --- .../GraphTypes/FungibleIdAndCountInputType.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 NineChronicles.Headless/GraphTypes/FungibleIdAndCountInputType.cs diff --git a/NineChronicles.Headless/GraphTypes/FungibleIdAndCountInputType.cs b/NineChronicles.Headless/GraphTypes/FungibleIdAndCountInputType.cs new file mode 100644 index 000000000..5baa5dccd --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/FungibleIdAndCountInputType.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using GraphQL.Types; +using Libplanet; +using System.Security.Cryptography; + +namespace NineChronicles.Headless.GraphTypes.Input +{ + public class FungibleIdAndCountInputType : + InputObjectGraphType<(HashDigest fungibleId, uint count)> + { + public FungibleIdAndCountInputType() + { + Name = "FungibleIdAndCountInput"; + + Field>( + name: "fungibleId", + description: "Fungible ID"); + + Field( + name: "count", + description: "Count"); + } + + public override object ParseDictionary(IDictionary value) + { + var fungibleId = (HashDigest)value["fungibleId"]!; + var count = (uint)value["count"]!; + return (fungibleId, count); + } + } +} From 4786d734170d0c6aa16fcd4508e72c7e9a9ba7db Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Wed, 7 Jun 2023 21:31:47 +0900 Subject: [PATCH 04/89] Introduce SimplifyCurrencyInputType --- .../GraphTypes/SimplifyCurrencyInputType.cs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 NineChronicles.Headless/GraphTypes/SimplifyCurrencyInputType.cs diff --git a/NineChronicles.Headless/GraphTypes/SimplifyCurrencyInputType.cs b/NineChronicles.Headless/GraphTypes/SimplifyCurrencyInputType.cs new file mode 100644 index 000000000..900a7969a --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/SimplifyCurrencyInputType.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using GraphQL; +using GraphQL.Types; +using Lib9c; +using Libplanet; +using Libplanet.Assets; + +namespace NineChronicles.Headless.GraphTypes +{ + // FIXME: To StringGraphType. + public class SimplifyCurrencyInputType : InputObjectGraphType + { + /// + /// This NCG definition is for mainnet. + /// +#pragma warning disable CS0618 + public static readonly Currency NCG = Currency.Legacy( + "NCG", + 2, + new Address("0x47D082a115c63E7b58B1532d20E631538eaFADde")); +#pragma warning restore CS0618 + + public SimplifyCurrencyInputType() + { + Name = "SimplifyCurrencyInput"; + Description = "Generate currency from ticker. If ticker is NCG, it will return mainnet NCG.\n" + + " See also: Currency? Currencies.GetCurrency(string ticker) " + + "https://github.com/planetarium/lib9c/blob/main/Lib9c/Currencies.cs"; + Field>( + name: "ticker", + description: "Ticker"); + } + + public override object ParseDictionary(IDictionary value) + { + var ticker = (string)value["ticker"]!; + var currency = Currencies.GetCurrency(ticker); + if (currency is not null) + { + return currency; + } + + if (ticker == "NCG") + { + return NCG; + } + + throw new ExecutionError($"Invalid ticker: {ticker}"); + } + } +} From d15957d9efe9faccc2dd7be85171cae171909cc2 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Wed, 7 Jun 2023 21:32:03 +0900 Subject: [PATCH 05/89] Introduce SimplifyFungibleAssetValueInputType --- .../SimplifyFungibleAssetValueInputType.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 NineChronicles.Headless/GraphTypes/SimplifyFungibleAssetValueInputType.cs diff --git a/NineChronicles.Headless/GraphTypes/SimplifyFungibleAssetValueInputType.cs b/NineChronicles.Headless/GraphTypes/SimplifyFungibleAssetValueInputType.cs new file mode 100644 index 000000000..4f41fefcd --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/SimplifyFungibleAssetValueInputType.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Numerics; +using GraphQL.Types; +using Libplanet.Assets; + +namespace NineChronicles.Headless.GraphTypes +{ + public class SimplifyFungibleAssetValueInputType : InputObjectGraphType + { + public SimplifyFungibleAssetValueInputType() + { + Name = "SimplifyFungibleAssetValueInput"; + Field>( + name: "currency"); + Field>( + name: "majorUnit", + description: "Major unit of currency"); + Field>( + name: "minorUnit", + description: "Minor unit of currency"); + } + + public override object ParseDictionary(IDictionary value) + { + var currency = (Currency)value["currency"]!; + var majorUnit = (BigInteger)value["majorUnit"]!; + var minorUnit = (BigInteger)value["minorUnit"]!; + return new FungibleAssetValue(currency, majorUnit, minorUnit); + } + } +} From 2486f535ad41e6149c16a733ab2257af37106f86 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Wed, 7 Jun 2023 21:32:17 +0900 Subject: [PATCH 06/89] Introduce BalanceAddressAndFungibleAssetValueInputType --- ...ceAddressAndFungibleAssetValueInputType.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 NineChronicles.Headless/GraphTypes/BalanceAddressAndFungibleAssetValueInputType.cs diff --git a/NineChronicles.Headless/GraphTypes/BalanceAddressAndFungibleAssetValueInputType.cs b/NineChronicles.Headless/GraphTypes/BalanceAddressAndFungibleAssetValueInputType.cs new file mode 100644 index 000000000..354c64682 --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/BalanceAddressAndFungibleAssetValueInputType.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using GraphQL.Types; +using Libplanet; +using Libplanet.Assets; +using Libplanet.Explorer.GraphTypes; + +namespace NineChronicles.Headless.GraphTypes +{ + public class BalanceAddressAndSimplifyFungibleAssetValueTupleInputType : + InputObjectGraphType<(Address balanceAddr, FungibleAssetValue value)> + { + public BalanceAddressAndSimplifyFungibleAssetValueTupleInputType() + { + Name = "BalanceAddressAndFungibleAssetValueInput"; + + Field( + name: "balanceAddr", + description: "Balance address"); + + Field( + name: "value", + description: "Simplify fungible asset value"); + } + + public override object ParseDictionary(IDictionary value) + { + var balanceAddr = (Address)value["balanceAddr"]!; + var assetValue = (FungibleAssetValue)value["value"]!; + return (balanceAddr, assetValue); + } + } +} From 116f87662aab48f0e3d86f9f6740b22026142dc2 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Wed, 7 Jun 2023 21:32:34 +0900 Subject: [PATCH 07/89] Introduce LoadIntoMyGaragesArgsInputType --- .../Garages/LoadIntoMyGaragesArgsInputType.cs | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 NineChronicles.Headless/GraphTypes/ActionArgs/Garages/LoadIntoMyGaragesArgsInputType.cs diff --git a/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/LoadIntoMyGaragesArgsInputType.cs b/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/LoadIntoMyGaragesArgsInputType.cs new file mode 100644 index 000000000..146735a97 --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/LoadIntoMyGaragesArgsInputType.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using GraphQL.Types; +using Libplanet; +using Libplanet.Assets; +using Libplanet.Explorer.GraphTypes; +using NineChronicles.Headless.GraphTypes.Input; + +namespace NineChronicles.Headless.GraphTypes.ActionArgs.Garages +{ + public class LoadIntoMyGaragesArgsInputType : InputObjectGraphType<( + IEnumerable<(Address balanceAddr, FungibleAssetValue value)>? fungibleAssetValues, + Address? inventoryAddr, + IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts)> + { + public LoadIntoMyGaragesArgsInputType() + { + Name = "LoadIntoMyGaragesArgsInput"; + + Field>>( + name: "fungibleAssetValues", + description: "Array of balance address and currency ticker and quantity."); + + Field( + name: "inventoryAddr", + description: "Inventory address"); + + Field>>( + name: "fungibleIdAndCounts", + description: "Array of fungible ID and count"); + } + + public override object ParseDictionary(IDictionary value) + { + IEnumerable<(Address balanceAddr, FungibleAssetValue value)>? fungibleAssetValues = null; + if (value["fungibleAssetValues"] is object[] fungibleAssetValueObjects) + { + fungibleAssetValues = fungibleAssetValueObjects + .OfType<(Address, FungibleAssetValue)>() + .Select(tuple => (tuple.Item1, tuple.Item2)); + } + + var inventoryAddr = (Address?)value["inventoryAddr"]; + IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts = null; + if (value["fungibleIdAndCounts"] is object[] fungibleIdAndCountObjects) + { + fungibleIdAndCounts = fungibleIdAndCountObjects + .OfType<(HashDigest, uint)>() + .Select(tuple => (tuple.Item1, (int)tuple.Item2)); + } + + return (fungibleAssetValues, inventoryAddr, fungibleIdAndCounts); + } + } +} From 2f88dacef982fe5c59c17def7d92772e044ae450 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Wed, 7 Jun 2023 21:33:13 +0900 Subject: [PATCH 08/89] Introduce LoadIntoMyGarages action query --- .../GraphTypes/ActionQueryTest.cs | 48 +++++++++++++++++++ .../GraphTypes/ActionQuery.cs | 1 + .../GraphTypes/ActionQueryFields/Garages.cs | 42 ++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs diff --git a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs index 1c5eb969a..f3046b5f8 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs @@ -3,16 +3,19 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; +using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using Bencodex; using Bencodex.Types; using GraphQL.Execution; +using Lib9c; using Libplanet; using Libplanet.Assets; using Libplanet.Crypto; using Nekoyume; using Nekoyume.Action; +using Nekoyume.Action.Garages; using Nekoyume.Helper; using Nekoyume.Model; using Nekoyume.Model.EnumType; @@ -930,5 +933,50 @@ public async Task CombinationConsumable() Assert.Equal(slotIndex, action.slotIndex); Assert.Equal(recipeId, action.recipeId); } + + [Fact] + public async Task LoadIntoMyGarages() + { + var fungibleAssetValues = new[] + { + (balanceAddr: new PrivateKey().ToAddress(), value: new FungibleAssetValue(Currencies.Crystal, 1, 0)), + (balanceAddr: new PrivateKey().ToAddress(), value: new FungibleAssetValue(Currencies.Crystal, 1, 0)), + }; + var fungibleAssetValuesString = string.Join(",", fungibleAssetValues.Select(tuple => + $"{{balanceAddr: \"{tuple.balanceAddr.ToHex()}\"," + + $"value: {{currency: {{ ticker: \"{tuple.value.Currency.Ticker}\" }}," + + $"majorUnit: {tuple.value.MajorUnit}," + + $"minorUnit: {tuple.value.MinorUnit}}}}}")); + var inventoryAddr = new PrivateKey().ToAddress(); + var fungibleIdAndCounts = new[] + { + (fungibleId: new HashDigest(), count: 1), + (fungibleId: new HashDigest(), count: 1), + }; + var fungibleIdAndCountsString = string.Join(",", fungibleIdAndCounts.Select(tuple => + $"{{fungibleId: {{value: \"{tuple.fungibleId.ToString()}\"}}, count: {tuple.count}}}")); + var expectedAction = new LoadIntoMyGarages( + fungibleAssetValues, + inventoryAddr, + fungibleIdAndCounts); + var query = "{loadIntoMyGarages(args: {" + + $"fungibleAssetValues: [{fungibleAssetValuesString}], " + + $"inventoryAddr: \"{inventoryAddr.ToHex()}\", " + + $"fungibleIdAndCounts: [{fungibleIdAndCountsString}]" + + "})}"; + var queryResult = await ExecuteQueryAsync( + query, + standaloneContext: _standaloneContext); + Assert.Null(queryResult.Errors); + + var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; + var plainValue = _codec.Decode(ByteUtil.ParseHex((string)data["loadIntoMyGarages"])); + Assert.IsType(plainValue); + var actionBase = DeserializeNCAction(plainValue); + var action = Assert.IsType(actionBase); + Assert.Equal(expectedAction.FungibleAssetValues, action.FungibleAssetValues); + Assert.Equal(expectedAction.InventoryAddr, action.InventoryAddr); + Assert.Equal(expectedAction.FungibleIdAndCounts, action.FungibleIdAndCounts); + } } } diff --git a/NineChronicles.Headless/GraphTypes/ActionQuery.cs b/NineChronicles.Headless/GraphTypes/ActionQuery.cs index 0ce0752fa..49805d1bd 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQuery.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQuery.cs @@ -543,6 +543,7 @@ public ActionQuery(StandaloneContext standaloneContext) RegisterItemEnhancement(); RegisterRapidCombination(); RegisterCombinationConsumable(); + RegisterGarages(); Field>( name: "craftQuery", diff --git a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs new file mode 100644 index 000000000..1664eefac --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Security.Cryptography; +using GraphQL; +using GraphQL.Types; +using Libplanet; +using Libplanet.Assets; +using Libplanet.Explorer.GraphTypes; +using Nekoyume.Action; +using Nekoyume.Action.Garages; +using NineChronicles.Headless.GraphTypes.ActionArgs.Garages; + +namespace NineChronicles.Headless.GraphTypes; + +public partial class ActionQuery +{ + private void RegisterGarages() + { + Field>( + "LoadIntoMyGarages", + arguments: new QueryArguments( + new QueryArgument + { + Name = "args", + Description = "The arguments of the \"LoadIntoMyGarages\" action constructor.", + } + ), + resolve: context => + { + var args = context.GetArgument<( + IEnumerable<(Address balanceAddr, FungibleAssetValue value)>? fungibleAssetValues, + Address? inventoryAddr, + IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts + )>("args"); + ActionBase action = new LoadIntoMyGarages( + args.fungibleAssetValues, + args.inventoryAddr, + args.fungibleIdAndCounts); + return Encode(context, action); + } + ); + } +} From 8e80e0bdd345de5156e527ed4cb44a18a37e9fea Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Thu, 8 Jun 2023 11:25:47 +0900 Subject: [PATCH 09/89] Introduce DeliverToOthersGarages action query --- .../GraphTypes/ActionQueryTest.cs | 44 +++++++++++++++ .../DeliverToOthersGaragesArgsInputType.cs | 55 +++++++++++++++++++ .../GraphTypes/ActionQueryFields/Garages.cs | 24 ++++++++ 3 files changed, 123 insertions(+) create mode 100644 NineChronicles.Headless/GraphTypes/ActionArgs/Garages/DeliverToOthersGaragesArgsInputType.cs diff --git a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs index f3046b5f8..3e3ec6439 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs @@ -977,6 +977,50 @@ public async Task LoadIntoMyGarages() Assert.Equal(expectedAction.FungibleAssetValues, action.FungibleAssetValues); Assert.Equal(expectedAction.InventoryAddr, action.InventoryAddr); Assert.Equal(expectedAction.FungibleIdAndCounts, action.FungibleIdAndCounts); + + [Fact] + public async Task DeliverToOthersGarages() + { + var recipientAgentAddr = new PrivateKey().ToAddress(); + var fungibleAssetValues = new[] + { + new FungibleAssetValue(Currencies.Crystal, 1, 0), + new FungibleAssetValue(Currencies.Crystal, 1, 0), + }; + var fungibleAssetValuesString = string.Join(",", fungibleAssetValues.Select(fav => + $"{{ currency: {{ ticker: \"{fav.Currency.Ticker}\" }}, " + + $"majorUnit: {fav.MajorUnit}, " + + $"minorUnit: {fav.MinorUnit} }}")); + var fungibleIdAndCounts = new[] + { + (fungibleId: new HashDigest(), count: 1), + (fungibleId: new HashDigest(), count: 1), + }; + var fungibleIdAndCountsString = string.Join(",", fungibleIdAndCounts.Select(tuple => + $"{{ fungibleId: {{ value: \"{tuple.fungibleId.ToString()}\"}}, count: {tuple.count} }}")); + var expectedAction = new DeliverToOthersGarages( + recipientAgentAddr, + fungibleAssetValues, + fungibleIdAndCounts); + var query = "{ deliverToOthersGarages(args: { " + + $"recipientAgentAddr: \"{recipientAgentAddr.ToHex()}\", " + + $"fungibleAssetValues: [{fungibleAssetValuesString}], " + + $"fungibleIdAndCounts: [{fungibleIdAndCountsString}] " + + "}) }"; + var queryResult = await ExecuteQueryAsync( + query, + standaloneContext: _standaloneContext); + Assert.Null(queryResult.Errors); + + var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; + var plainValue = _codec.Decode(ByteUtil.ParseHex((string)data["deliverToOthersGarages"])); + Assert.IsType(plainValue); + var actionBase = DeserializeNCAction(plainValue); + var action = Assert.IsType(actionBase); + Assert.Equal(expectedAction.RecipientAgentAddr, action.RecipientAgentAddr); + Assert.True(expectedAction.FungibleAssetValues.SequenceEqual(action.FungibleAssetValues)); + Assert.True(expectedAction.FungibleIdAndCounts.SequenceEqual(action.FungibleIdAndCounts)); + } } } } diff --git a/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/DeliverToOthersGaragesArgsInputType.cs b/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/DeliverToOthersGaragesArgsInputType.cs new file mode 100644 index 000000000..1010f8435 --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/DeliverToOthersGaragesArgsInputType.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using GraphQL.Types; +using Libplanet; +using Libplanet.Assets; +using Libplanet.Explorer.GraphTypes; +using NineChronicles.Headless.GraphTypes.Input; + +namespace NineChronicles.Headless.GraphTypes.ActionArgs.Garages +{ + public class DeliverToOthersGaragesArgsInputType : InputObjectGraphType<( + Address recipientAgentAddr, + IEnumerable? fungibleAssetValues, + IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts)> + { + public DeliverToOthersGaragesArgsInputType() + { + Name = "DeliverToOthersGaragesArgsInput"; + + Field>( + name: "recipientAgentAddr", + description: "Recipient agent address."); + + Field>>( + name: "fungibleAssetValues", + description: "Array of currency ticker and quantity."); + + Field>>( + name: "fungibleIdAndCounts", + description: "Array of fungible ID and count."); + } + + public override object ParseDictionary(IDictionary value) + { + var recipientAgentAddr = (Address)value["recipientAgentAddr"]!; + IEnumerable? fungibleAssetValues = null; + if (value["fungibleAssetValues"] is object[] fungibleAssetValueObjects) + { + fungibleAssetValues = fungibleAssetValueObjects + .OfType(); + } + + IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts = null; + if (value["fungibleIdAndCounts"] is object[] fungibleIdAndCountObjects) + { + fungibleIdAndCounts = fungibleIdAndCountObjects + .OfType<(HashDigest, uint)>() + .Select(tuple => (tuple.Item1, (int)tuple.Item2)); + } + + return (recipientAgentAddr, fungibleAssetValues, fungibleIdAndCounts); + } + } +} diff --git a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs index 1664eefac..0754f8fd8 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs @@ -38,5 +38,29 @@ private void RegisterGarages() return Encode(context, action); } ); + + Field>( + "deliverToOthersGarages", + arguments: new QueryArguments( + new QueryArgument + { + Name = "args", + Description = "The arguments of the \"DeliverToOthersGarages\" action constructor.", + } + ), + resolve: context => + { + var args = context.GetArgument<( + Address recipientAgentAddr, + IEnumerable? fungibleAssetValues, + IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts + )>("args"); + ActionBase action = new DeliverToOthersGarages( + args.recipientAgentAddr, + args.fungibleAssetValues, + args.fungibleIdAndCounts); + return Encode(context, action); + } + ); } } From 8e3b705edaed5bf2359b494949767f88af2a8755 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Thu, 8 Jun 2023 11:26:14 +0900 Subject: [PATCH 10/89] Introduce UnloadFromMyGarages action query --- .../GraphTypes/ActionQueryTest.cs | 44 +++++++++++++++ .../UnloadFromMyGaragesArgsInputType.cs | 56 +++++++++++++++++++ .../GraphTypes/ActionQueryFields/Garages.cs | 24 ++++++++ 3 files changed, 124 insertions(+) create mode 100644 NineChronicles.Headless/GraphTypes/ActionArgs/Garages/UnloadFromMyGaragesArgsInputType.cs diff --git a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs index 3e3ec6439..227b48979 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs @@ -1021,6 +1021,50 @@ public async Task DeliverToOthersGarages() Assert.True(expectedAction.FungibleAssetValues.SequenceEqual(action.FungibleAssetValues)); Assert.True(expectedAction.FungibleIdAndCounts.SequenceEqual(action.FungibleIdAndCounts)); } + + [Fact] + public async Task UnloadFromMyGarages() + { + var fungibleAssetValues = new[] + { + (balanceAddr: new PrivateKey().ToAddress(), value: new FungibleAssetValue(Currencies.Crystal, 1, 0)), + (balanceAddr: new PrivateKey().ToAddress(), value: new FungibleAssetValue(Currencies.Crystal, 1, 0)), + }; + var fungibleAssetValuesString = string.Join(",", fungibleAssetValues.Select(tuple => + $"{{ balanceAddr: \"{tuple.balanceAddr.ToHex()}\", " + + $"value: {{ currency: {{ ticker: \"{tuple.value.Currency.Ticker}\" }}, " + + $"majorUnit: {tuple.value.MajorUnit}, " + + $"minorUnit: {tuple.value.MinorUnit} }} }}")); + var inventoryAddr = new PrivateKey().ToAddress(); + var fungibleIdAndCounts = new[] + { + (fungibleId: new HashDigest(), count: 1), + (fungibleId: new HashDigest(), count: 1), + }; + var fungibleIdAndCountsString = string.Join(",", fungibleIdAndCounts.Select(tuple => + $"{{ fungibleId: {{ value: \"{tuple.fungibleId.ToString()}\"}}, count: {tuple.count} }}")); + var expectedAction = new LoadIntoMyGarages( + fungibleAssetValues, + inventoryAddr, + fungibleIdAndCounts); + var query = "{ unloadFromMyGarages(args: { " + + $"fungibleAssetValues: [{fungibleAssetValuesString}], " + + $"inventoryAddr: \"{inventoryAddr.ToHex()}\", " + + $"fungibleIdAndCounts: [{fungibleIdAndCountsString}]" + + "}) }"; + var queryResult = await ExecuteQueryAsync( + query, + standaloneContext: _standaloneContext); + Assert.Null(queryResult.Errors); + + var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; + var plainValue = _codec.Decode(ByteUtil.ParseHex((string)data["unloadFromMyGarages"])); + Assert.IsType(plainValue); + var actionBase = DeserializeNCAction(plainValue); + var action = Assert.IsType(actionBase); + Assert.True(expectedAction.FungibleAssetValues.SequenceEqual(action.FungibleAssetValues)); + Assert.Equal(expectedAction.InventoryAddr, action.InventoryAddr); + Assert.True(expectedAction.FungibleIdAndCounts.SequenceEqual(action.FungibleIdAndCounts)); } } } diff --git a/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/UnloadFromMyGaragesArgsInputType.cs b/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/UnloadFromMyGaragesArgsInputType.cs new file mode 100644 index 000000000..e69436ecf --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/UnloadFromMyGaragesArgsInputType.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using GraphQL.Types; +using Libplanet; +using Libplanet.Assets; +using Libplanet.Explorer.GraphTypes; +using NineChronicles.Headless.GraphTypes.Input; + +namespace NineChronicles.Headless.GraphTypes.ActionArgs.Garages +{ + public class UnloadFromMyGaragesArgsInputType : InputObjectGraphType<( + IEnumerable<(Address balanceAddr, FungibleAssetValue value)>? fungibleAssetValues, + Address? inventoryAddr, + IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts)> + { + public UnloadFromMyGaragesArgsInputType() + { + Name = "UnloadFromMyGaragesArgsInput"; + + Field>>( + name: "fungibleAssetValues", + description: "Array of balance address and currency ticker and quantity."); + + Field( + name: "inventoryAddr", + description: "Inventory address"); + + Field>>( + name: "fungibleIdAndCounts", + description: "Array of fungible ID and count"); + } + + public override object ParseDictionary(IDictionary value) + { + IEnumerable<(Address balanceAddr, FungibleAssetValue value)>? fungibleAssetValues = null; + if (value["fungibleAssetValues"] is object[] fungibleAssetValueObjects) + { + fungibleAssetValues = fungibleAssetValueObjects + .OfType<(Address, FungibleAssetValue)>() + .Select(tuple => (tuple.Item1, tuple.Item2)); + } + + var inventoryAddr = (Address?)value["inventoryAddr"]; + IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts = null; + if (value["fungibleIdAndCounts"] is object[] fungibleIdAndCountObjects) + { + fungibleIdAndCounts = fungibleIdAndCountObjects + .OfType<(HashDigest, uint)>() + .Select(tuple => (tuple.Item1, (int)tuple.Item2)); + } + + return (fungibleAssetValues, inventoryAddr, fungibleIdAndCounts); + } + } +} diff --git a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs index 0754f8fd8..3f66f4957 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs @@ -62,5 +62,29 @@ private void RegisterGarages() return Encode(context, action); } ); + + Field>( + "unloadFromMyGarages", + arguments: new QueryArguments( + new QueryArgument + { + Name = "args", + Description = "The arguments of the \"UnloadFromMyGarages\" action constructor.", + } + ), + resolve: context => + { + var args = context.GetArgument<( + IEnumerable<(Address balanceAddr, FungibleAssetValue value)>? fungibleAssetValues, + Address? inventoryAddr, + IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts + )>("args"); + ActionBase action = new UnloadFromMyGarages( + args.fungibleAssetValues, + args.inventoryAddr, + args.fungibleIdAndCounts); + return Encode(context, action); + } + ); } } From 498d1ad711443b14e51b9a9727c21f30a4b8a317 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Thu, 8 Jun 2023 11:26:48 +0900 Subject: [PATCH 11/89] Fix texts minor --- .../GraphTypes/ActionQueryTest.cs | 26 ++-- .../GraphTypes/ActionQueryFields/Garages.cs | 137 +++++++++--------- .../GraphTypes/SimplifyCurrencyInputType.cs | 2 +- .../SimplifyFungibleAssetValueInputType.cs | 4 +- 4 files changed, 85 insertions(+), 84 deletions(-) diff --git a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs index 227b48979..8ca8e8500 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs @@ -55,8 +55,7 @@ public ActionQueryTest() activatedAccountsState: new ActivatedAccountsState(), #pragma warning disable CS0618 // Use of obsolete method Currency.Legacy(): https://github.com/planetarium/lib9c/discussions/1319 - goldCurrencyState: - new GoldCurrencyState(Currency.Legacy("NCG", 2, minerPrivateKey.ToAddress())), + goldCurrencyState: new GoldCurrencyState(Currency.Legacy("NCG", 2, minerPrivateKey.ToAddress())), #pragma warning restore CS0618 goldDistributions: Array.Empty(), tableSheets: new Dictionary(), @@ -943,10 +942,10 @@ public async Task LoadIntoMyGarages() (balanceAddr: new PrivateKey().ToAddress(), value: new FungibleAssetValue(Currencies.Crystal, 1, 0)), }; var fungibleAssetValuesString = string.Join(",", fungibleAssetValues.Select(tuple => - $"{{balanceAddr: \"{tuple.balanceAddr.ToHex()}\"," + - $"value: {{currency: {{ ticker: \"{tuple.value.Currency.Ticker}\" }}," + - $"majorUnit: {tuple.value.MajorUnit}," + - $"minorUnit: {tuple.value.MinorUnit}}}}}")); + $"{{ balanceAddr: \"{tuple.balanceAddr.ToHex()}\", " + + $"value: {{ currency: {{ ticker: \"{tuple.value.Currency.Ticker}\" }}, " + + $"majorUnit: {tuple.value.MajorUnit}, " + + $"minorUnit: {tuple.value.MinorUnit} }} }}")); var inventoryAddr = new PrivateKey().ToAddress(); var fungibleIdAndCounts = new[] { @@ -954,16 +953,16 @@ public async Task LoadIntoMyGarages() (fungibleId: new HashDigest(), count: 1), }; var fungibleIdAndCountsString = string.Join(",", fungibleIdAndCounts.Select(tuple => - $"{{fungibleId: {{value: \"{tuple.fungibleId.ToString()}\"}}, count: {tuple.count}}}")); + $"{{ fungibleId: {{ value: \"{tuple.fungibleId.ToString()}\"}}, count: {tuple.count} }}")); var expectedAction = new LoadIntoMyGarages( fungibleAssetValues, inventoryAddr, fungibleIdAndCounts); - var query = "{loadIntoMyGarages(args: {" + + var query = "{ loadIntoMyGarages(args: { " + $"fungibleAssetValues: [{fungibleAssetValuesString}], " + $"inventoryAddr: \"{inventoryAddr.ToHex()}\", " + $"fungibleIdAndCounts: [{fungibleIdAndCountsString}]" + - "})}"; + "}) }"; var queryResult = await ExecuteQueryAsync( query, standaloneContext: _standaloneContext); @@ -974,10 +973,11 @@ public async Task LoadIntoMyGarages() Assert.IsType(plainValue); var actionBase = DeserializeNCAction(plainValue); var action = Assert.IsType(actionBase); - Assert.Equal(expectedAction.FungibleAssetValues, action.FungibleAssetValues); + Assert.True(expectedAction.FungibleAssetValues.SequenceEqual(action.FungibleAssetValues)); Assert.Equal(expectedAction.InventoryAddr, action.InventoryAddr); - Assert.Equal(expectedAction.FungibleIdAndCounts, action.FungibleIdAndCounts); - + Assert.True(expectedAction.FungibleIdAndCounts.SequenceEqual(action.FungibleIdAndCounts)); + } + [Fact] public async Task DeliverToOthersGarages() { @@ -1021,7 +1021,7 @@ public async Task DeliverToOthersGarages() Assert.True(expectedAction.FungibleAssetValues.SequenceEqual(action.FungibleAssetValues)); Assert.True(expectedAction.FungibleIdAndCounts.SequenceEqual(action.FungibleIdAndCounts)); } - + [Fact] public async Task UnloadFromMyGarages() { diff --git a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs index 3f66f4957..a1bf7dcf0 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs @@ -9,82 +9,83 @@ using Nekoyume.Action.Garages; using NineChronicles.Headless.GraphTypes.ActionArgs.Garages; -namespace NineChronicles.Headless.GraphTypes; - -public partial class ActionQuery +namespace NineChronicles.Headless.GraphTypes { - private void RegisterGarages() + public partial class ActionQuery { - Field>( - "LoadIntoMyGarages", - arguments: new QueryArguments( - new QueryArgument + private void RegisterGarages() + { + Field>( + "loadIntoMyGarages", + arguments: new QueryArguments( + new QueryArgument + { + Name = "args", + Description = "The arguments of the \"LoadIntoMyGarages\" action constructor.", + } + ), + resolve: context => { - Name = "args", - Description = "The arguments of the \"LoadIntoMyGarages\" action constructor.", + var args = context.GetArgument<( + IEnumerable<(Address balanceAddr, FungibleAssetValue value)>? fungibleAssetValues, + Address? inventoryAddr, + IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts + )>("args"); + ActionBase action = new LoadIntoMyGarages( + args.fungibleAssetValues, + args.inventoryAddr, + args.fungibleIdAndCounts); + return Encode(context, action); } - ), - resolve: context => - { - var args = context.GetArgument<( - IEnumerable<(Address balanceAddr, FungibleAssetValue value)>? fungibleAssetValues, - Address? inventoryAddr, - IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts - )>("args"); - ActionBase action = new LoadIntoMyGarages( - args.fungibleAssetValues, - args.inventoryAddr, - args.fungibleIdAndCounts); - return Encode(context, action); - } - ); + ); - Field>( - "deliverToOthersGarages", - arguments: new QueryArguments( - new QueryArgument + Field>( + "deliverToOthersGarages", + arguments: new QueryArguments( + new QueryArgument + { + Name = "args", + Description = "The arguments of the \"DeliverToOthersGarages\" action constructor.", + } + ), + resolve: context => { - Name = "args", - Description = "The arguments of the \"DeliverToOthersGarages\" action constructor.", + var args = context.GetArgument<( + Address recipientAgentAddr, + IEnumerable? fungibleAssetValues, + IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts + )>("args"); + ActionBase action = new DeliverToOthersGarages( + args.recipientAgentAddr, + args.fungibleAssetValues, + args.fungibleIdAndCounts); + return Encode(context, action); } - ), - resolve: context => - { - var args = context.GetArgument<( - Address recipientAgentAddr, - IEnumerable? fungibleAssetValues, - IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts - )>("args"); - ActionBase action = new DeliverToOthersGarages( - args.recipientAgentAddr, - args.fungibleAssetValues, - args.fungibleIdAndCounts); - return Encode(context, action); - } - ); - - Field>( - "unloadFromMyGarages", - arguments: new QueryArguments( - new QueryArgument + ); + + Field>( + "unloadFromMyGarages", + arguments: new QueryArguments( + new QueryArgument + { + Name = "args", + Description = "The arguments of the \"UnloadFromMyGarages\" action constructor.", + } + ), + resolve: context => { - Name = "args", - Description = "The arguments of the \"UnloadFromMyGarages\" action constructor.", + var args = context.GetArgument<( + IEnumerable<(Address balanceAddr, FungibleAssetValue value)>? fungibleAssetValues, + Address? inventoryAddr, + IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts + )>("args"); + ActionBase action = new UnloadFromMyGarages( + args.fungibleAssetValues, + args.inventoryAddr, + args.fungibleIdAndCounts); + return Encode(context, action); } - ), - resolve: context => - { - var args = context.GetArgument<( - IEnumerable<(Address balanceAddr, FungibleAssetValue value)>? fungibleAssetValues, - Address? inventoryAddr, - IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts - )>("args"); - ActionBase action = new UnloadFromMyGarages( - args.fungibleAssetValues, - args.inventoryAddr, - args.fungibleIdAndCounts); - return Encode(context, action); - } - ); + ); + } } } diff --git a/NineChronicles.Headless/GraphTypes/SimplifyCurrencyInputType.cs b/NineChronicles.Headless/GraphTypes/SimplifyCurrencyInputType.cs index 900a7969a..f1365b543 100644 --- a/NineChronicles.Headless/GraphTypes/SimplifyCurrencyInputType.cs +++ b/NineChronicles.Headless/GraphTypes/SimplifyCurrencyInputType.cs @@ -28,7 +28,7 @@ public SimplifyCurrencyInputType() "https://github.com/planetarium/lib9c/blob/main/Lib9c/Currencies.cs"; Field>( name: "ticker", - description: "Ticker"); + description: "Ticker."); } public override object ParseDictionary(IDictionary value) diff --git a/NineChronicles.Headless/GraphTypes/SimplifyFungibleAssetValueInputType.cs b/NineChronicles.Headless/GraphTypes/SimplifyFungibleAssetValueInputType.cs index 4f41fefcd..6bd4381ce 100644 --- a/NineChronicles.Headless/GraphTypes/SimplifyFungibleAssetValueInputType.cs +++ b/NineChronicles.Headless/GraphTypes/SimplifyFungibleAssetValueInputType.cs @@ -14,10 +14,10 @@ public SimplifyFungibleAssetValueInputType() name: "currency"); Field>( name: "majorUnit", - description: "Major unit of currency"); + description: "Major unit of currency quantity."); Field>( name: "minorUnit", - description: "Minor unit of currency"); + description: "Minor unit of currency quantity."); } public override object ParseDictionary(IDictionary value) From 4c5603b7b753f144f764c607cbf1f83387850771 Mon Sep 17 00:00:00 2001 From: hyeon Date: Thu, 15 Jun 2023 17:58:51 +0900 Subject: [PATCH 12/89] Update GQL query form - Reform BalanceAddressAndFungibleAssetValueInputType - Create FungibleAssetValueInputType for reusability - Apply to LoadIntoMyGarage first --- .../Garages/LoadIntoMyGaragesArgsInputType.cs | 2 +- .../GraphTypes/ActionQueryFields/Garages.cs | 71 +++++++++++++++---- ...geAddressAndFungibleAssetValueInputType.cs | 35 +++++++++ .../GarageFungibleAssetValueInputType.cs | 35 +++++++++ 4 files changed, 130 insertions(+), 13 deletions(-) create mode 100644 NineChronicles.Headless/GraphTypes/GarageAddressAndFungibleAssetValueInputType.cs create mode 100644 NineChronicles.Headless/GraphTypes/GarageFungibleAssetValueInputType.cs diff --git a/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/LoadIntoMyGaragesArgsInputType.cs b/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/LoadIntoMyGaragesArgsInputType.cs index 146735a97..fb2953613 100644 --- a/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/LoadIntoMyGaragesArgsInputType.cs +++ b/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/LoadIntoMyGaragesArgsInputType.cs @@ -18,7 +18,7 @@ public LoadIntoMyGaragesArgsInputType() { Name = "LoadIntoMyGaragesArgsInput"; - Field>>( + Field>>( name: "fungibleAssetValues", description: "Array of balance address and currency ticker and quantity."); diff --git a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs index a1bf7dcf0..358b136b6 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; +using System.Numerics; using System.Security.Cryptography; +using Bencodex.Types; using GraphQL; using GraphQL.Types; using Libplanet; @@ -7,7 +9,10 @@ using Libplanet.Explorer.GraphTypes; using Nekoyume.Action; using Nekoyume.Action.Garages; +using Nekoyume.Helper; +using Nekoyume.Model.State; using NineChronicles.Headless.GraphTypes.ActionArgs.Garages; +using NineChronicles.Headless.GraphTypes.Input; namespace NineChronicles.Headless.GraphTypes { @@ -18,23 +23,65 @@ private void RegisterGarages() Field>( "loadIntoMyGarages", arguments: new QueryArguments( - new QueryArgument + new QueryArgument>> { - Name = "args", - Description = "The arguments of the \"LoadIntoMyGarages\" action constructor.", + Name = "addressAndFungibleAssetValues", + Description = "Array of balance address and currency ticker and quantity.", + }, + new QueryArgument + { + Name = "inventoryAddr", + Description = "Inventory Address", + }, + new QueryArgument>> + { + Name = "fungibleIdAndCounts", + Description = "Array of fungible ID and count" } ), - resolve: context => + resolve: + context => { - var args = context.GetArgument<( - IEnumerable<(Address balanceAddr, FungibleAssetValue value)>? fungibleAssetValues, - Address? inventoryAddr, - IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts - )>("args"); + // This transforms to (Address, FungibleAssetValue) and goes to.... + var addressAndFavList = context + .GetArgument>("addressAndFungibleAssetValues"); + // Here. and This is the input type of action. + var fungibleAssetValues = new List<(Address balanceAddr, FungibleAssetValue value)>(); + + foreach (var (addr, (curr, majorUnit, minorUnit)) + in addressAndFavList) + { + Currency currency; + switch (curr) + { + case CurrencyEnum.NCG: + currency = new GoldCurrencyState( + (Dictionary)standaloneContext.BlockChain!.GetState(GoldCurrencyState.Address) + ).Currency; + break; + case CurrencyEnum.CRYSTAL: + currency = CrystalCalculator.CRYSTAL; + break; + default: + throw new ExecutionError($"Unsupported Currency type {curr}"); + } + + FungibleAssetValue fav = new FungibleAssetValue(currency, majorUnit, minorUnit); + fungibleAssetValues.Add((addr, fav)); + } + + var inventoryAddr = context.GetArgument("inventoryAddr"); + var fungibleIdAndCounts = + context.GetArgument fungibleId, int count)>?>( + "fungibleIdAndCounts"); + ActionBase action = new LoadIntoMyGarages( - args.fungibleAssetValues, - args.inventoryAddr, - args.fungibleIdAndCounts); + fungibleAssetValues, + inventoryAddr, + fungibleIdAndCounts); return Encode(context, action); } ); diff --git a/NineChronicles.Headless/GraphTypes/GarageAddressAndFungibleAssetValueInputType.cs b/NineChronicles.Headless/GraphTypes/GarageAddressAndFungibleAssetValueInputType.cs new file mode 100644 index 000000000..9f4c8e116 --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/GarageAddressAndFungibleAssetValueInputType.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Numerics; +using GraphQL.Types; +using Libplanet; +using Libplanet.Assets; +using Libplanet.Explorer.GraphTypes; + +namespace NineChronicles.Headless.GraphTypes +{ + public class GarageAddressAndFungibleAssetValueInputType : + InputObjectGraphType<(Address balanceAddr, GarageFungibleAssetValueInputType fav)> + { + public GarageAddressAndFungibleAssetValueInputType() + { + Name = "GarageAddressAndFungibleAssetValueInput"; + + Field( + name: "balanceAddr", + description: "Balance Address." + ); + + Field( + name: "fungibleAssetValue", + description: "Fungible asset value ticker and amount." + ); + } + + public override object ParseDictionary(IDictionary value) + { + var addr = (Address)value["balanceAddr"]!; + var fav = ((CurrencyEnum, BigInteger, BigInteger))value["fungibleAssetValue"]!; + return (addr, fav); + } + } +} diff --git a/NineChronicles.Headless/GraphTypes/GarageFungibleAssetValueInputType.cs b/NineChronicles.Headless/GraphTypes/GarageFungibleAssetValueInputType.cs new file mode 100644 index 000000000..5e4e299e9 --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/GarageFungibleAssetValueInputType.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Numerics; +using GraphQL.Types; + +namespace NineChronicles.Headless.GraphTypes +{ + public class GarageFungibleAssetValueInputType : + InputObjectGraphType<(CurrencyEnum currency, BigInteger majorUnit, BigInteger minorUnit)> + { + public GarageFungibleAssetValueInputType() + { + Name = "GarageFungibleAssetValueInput"; + + Field( + name: "currency", + description: "A currency type to be loaded."); + + Field>( + name: "majorUnit", + description: "Major unit of currency quantity."); + + Field>( + name: "minorUnit", + description: "Minor unit of currency quantity."); + } + + public override object ParseDictionary(IDictionary value) + { + var currency = (CurrencyEnum)value["currency"]!; + var majorUnit = (BigInteger)value["majorUnit"]!; + var minorUnit = (BigInteger)value["minorUnit"]!; + return (currency, majorUnit, minorUnit); + } + } +} From 819a8e7304a876ca2272609ebdaf1b00b9ae39d5 Mon Sep 17 00:00:00 2001 From: hyeon Date: Fri, 16 Jun 2023 10:57:36 +0900 Subject: [PATCH 13/89] Update Deliver GQL query to use GarageFAVInputType --- .../DeliverToOthersGaragesArgsInputType.cs | 2 +- .../GraphTypes/ActionQueryFields/Garages.cs | 82 ++++++++++++------- 2 files changed, 54 insertions(+), 30 deletions(-) diff --git a/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/DeliverToOthersGaragesArgsInputType.cs b/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/DeliverToOthersGaragesArgsInputType.cs index 1010f8435..b9923de2c 100644 --- a/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/DeliverToOthersGaragesArgsInputType.cs +++ b/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/DeliverToOthersGaragesArgsInputType.cs @@ -22,7 +22,7 @@ public DeliverToOthersGaragesArgsInputType() name: "recipientAgentAddr", description: "Recipient agent address."); - Field>>( + Field>>( name: "fungibleAssetValues", description: "Array of currency ticker and quantity."); diff --git a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs index 358b136b6..ca756ee74 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs @@ -18,6 +18,27 @@ namespace NineChronicles.Headless.GraphTypes { public partial class ActionQuery { + private FungibleAssetValue getFungibleAssetValue( + CurrencyEnum curr, BigInteger majorUnit, BigInteger minorUnit) + { + Currency currency; + switch (curr) + { + case CurrencyEnum.NCG: + currency = new GoldCurrencyState( + (Dictionary)standaloneContext.BlockChain!.GetState(GoldCurrencyState.Address) + ).Currency; + break; + case CurrencyEnum.CRYSTAL: + currency = CrystalCalculator.CRYSTAL; + break; + default: + throw new ExecutionError($"Unsupported Currency type {curr}"); + } + + return new FungibleAssetValue(currency, majorUnit, minorUnit); + } + private void RegisterGarages() { Field>( @@ -51,25 +72,9 @@ private void RegisterGarages() // Here. and This is the input type of action. var fungibleAssetValues = new List<(Address balanceAddr, FungibleAssetValue value)>(); - foreach (var (addr, (curr, majorUnit, minorUnit)) - in addressAndFavList) + foreach (var (addr, (curr, majorUnit, minorUnit)) in addressAndFavList) { - Currency currency; - switch (curr) - { - case CurrencyEnum.NCG: - currency = new GoldCurrencyState( - (Dictionary)standaloneContext.BlockChain!.GetState(GoldCurrencyState.Address) - ).Currency; - break; - case CurrencyEnum.CRYSTAL: - currency = CrystalCalculator.CRYSTAL; - break; - default: - throw new ExecutionError($"Unsupported Currency type {curr}"); - } - - FungibleAssetValue fav = new FungibleAssetValue(currency, majorUnit, minorUnit); + var fav = getFungibleAssetValue(curr, majorUnit, minorUnit); fungibleAssetValues.Add((addr, fav)); } @@ -89,23 +94,42 @@ private void RegisterGarages() Field>( "deliverToOthersGarages", arguments: new QueryArguments( - new QueryArgument + new QueryArgument> { - Name = "args", - Description = "The arguments of the \"DeliverToOthersGarages\" action constructor.", + Name = "recipientAgentAddr", + Description = "Recipient agent address", + }, + new QueryArgument>> + { + Name = "fungibleAssetValues", + Description = "Array of currency ticket and quantity to deliver." + }, + new QueryArgument>> + { + Name = "fungibleIdAndCounts", + Description = "Array of Fungible ID and count to deliver." } ), resolve: context => { - var args = context.GetArgument<( - Address recipientAgentAddr, - IEnumerable? fungibleAssetValues, - IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts - )>("args"); + var recipientAgentAddr = context.GetArgument
("recipientAgentAddr"); + var fungibleAssetValueList = context.GetArgument< + IEnumerable<(CurrencyEnum CurrencyEnum, BigInteger majorUnit, BigInteger minorUnit)> + >("fungibleAssetValues"); + var fungibleAssetValues = new List(); + foreach (var (curr, majorUnit, minorUnit) in fungibleAssetValueList) + { + fungibleAssetValues.Add(getFungibleAssetValue(curr, majorUnit, minorUnit)); + } + + var fungibleIdAndCounts = + context.GetArgument fungibleId, int count)>?>( + "fungibleIdAndCounts"); + ActionBase action = new DeliverToOthersGarages( - args.recipientAgentAddr, - args.fungibleAssetValues, - args.fungibleIdAndCounts); + recipientAgentAddr, + fungibleAssetValues, + fungibleIdAndCounts); return Encode(context, action); } ); From fafd360d3c76f392c9b4227b33eeaf06f1c0a3cb Mon Sep 17 00:00:00 2001 From: hyeon Date: Fri, 16 Jun 2023 11:04:48 +0900 Subject: [PATCH 14/89] Update Unload GQL query to use GarageFAVInputType --- .../UnloadFromMyGaragesArgsInputType.cs | 2 +- .../GraphTypes/ActionQueryFields/Garages.cs | 47 +++++++++++++------ ...ceAddressAndFungibleAssetValueInputType.cs | 32 ------------- 3 files changed, 34 insertions(+), 47 deletions(-) delete mode 100644 NineChronicles.Headless/GraphTypes/BalanceAddressAndFungibleAssetValueInputType.cs diff --git a/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/UnloadFromMyGaragesArgsInputType.cs b/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/UnloadFromMyGaragesArgsInputType.cs index e69436ecf..2ce518890 100644 --- a/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/UnloadFromMyGaragesArgsInputType.cs +++ b/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/UnloadFromMyGaragesArgsInputType.cs @@ -18,7 +18,7 @@ public UnloadFromMyGaragesArgsInputType() { Name = "UnloadFromMyGaragesArgsInput"; - Field>>( + Field>>( name: "fungibleAssetValues", description: "Array of balance address and currency ticker and quantity."); diff --git a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs index ca756ee74..ff28f71a5 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs @@ -11,7 +11,6 @@ using Nekoyume.Action.Garages; using Nekoyume.Helper; using Nekoyume.Model.State; -using NineChronicles.Headless.GraphTypes.ActionArgs.Garages; using NineChronicles.Headless.GraphTypes.Input; namespace NineChronicles.Headless.GraphTypes @@ -61,8 +60,7 @@ private void RegisterGarages() Description = "Array of fungible ID and count" } ), - resolve: - context => + resolve: context => { // This transforms to (Address, FungibleAssetValue) and goes to.... var addressAndFavList = context @@ -137,23 +135,44 @@ private void RegisterGarages() Field>( "unloadFromMyGarages", arguments: new QueryArguments( - new QueryArgument + new QueryArgument>> + { + Name = "addressAndungibleAssetValues", + Description = "Array of balance address and currency ticker and quantity to send." + }, + new QueryArgument + { + Name = "inventoryAddr", + Description = "Inventory address to receive items." + }, + new QueryArgument>> { - Name = "args", - Description = "The arguments of the \"UnloadFromMyGarages\" action constructor.", + Name = "fungibleIdAndCounts", + Description = "Array of fungible ID and count to send." } ), resolve: context => { - var args = context.GetArgument<( - IEnumerable<(Address balanceAddr, FungibleAssetValue value)>? fungibleAssetValues, - Address? inventoryAddr, - IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts - )>("args"); + var addressAndFavList = context + .GetArgument>("addressAndFungibleAssetValues"); + var fungibleAssetValues = new List<(Address balanceAddr, FungibleAssetValue value)>(); + foreach (var (addr, (curr, majorUnit, minorUnit)) in addressAndFavList) + { + var fav = getFungibleAssetValue(curr, majorUnit, minorUnit); + fungibleAssetValues.Add((addr, fav)); + } + + var inventoryAddr = context.GetArgument("inventoryAddr"); + var fungibleIdAndCounts = + context.GetArgument fungibleId, int count)>?>( + "fungibleIdAndCounts"); + ActionBase action = new UnloadFromMyGarages( - args.fungibleAssetValues, - args.inventoryAddr, - args.fungibleIdAndCounts); + fungibleAssetValues, + inventoryAddr, + fungibleIdAndCounts); return Encode(context, action); } ); diff --git a/NineChronicles.Headless/GraphTypes/BalanceAddressAndFungibleAssetValueInputType.cs b/NineChronicles.Headless/GraphTypes/BalanceAddressAndFungibleAssetValueInputType.cs deleted file mode 100644 index 354c64682..000000000 --- a/NineChronicles.Headless/GraphTypes/BalanceAddressAndFungibleAssetValueInputType.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Collections.Generic; -using GraphQL.Types; -using Libplanet; -using Libplanet.Assets; -using Libplanet.Explorer.GraphTypes; - -namespace NineChronicles.Headless.GraphTypes -{ - public class BalanceAddressAndSimplifyFungibleAssetValueTupleInputType : - InputObjectGraphType<(Address balanceAddr, FungibleAssetValue value)> - { - public BalanceAddressAndSimplifyFungibleAssetValueTupleInputType() - { - Name = "BalanceAddressAndFungibleAssetValueInput"; - - Field( - name: "balanceAddr", - description: "Balance address"); - - Field( - name: "value", - description: "Simplify fungible asset value"); - } - - public override object ParseDictionary(IDictionary value) - { - var balanceAddr = (Address)value["balanceAddr"]!; - var assetValue = (FungibleAssetValue)value["value"]!; - return (balanceAddr, assetValue); - } - } -} From 82301381e44b315d98a6d5b602a646c8035409fb Mon Sep 17 00:00:00 2001 From: hyeon Date: Fri, 16 Jun 2023 12:03:05 +0900 Subject: [PATCH 15/89] Fixations - Fix typo - Change Fungible count from uint to int to prevent casting prob. --- .../GraphTypes/ActionQueryFields/Garages.cs | 2 +- .../GraphTypes/FungibleIdAndCountInputType.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs index ff28f71a5..87be4c76e 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs @@ -137,7 +137,7 @@ private void RegisterGarages() arguments: new QueryArguments( new QueryArgument>> { - Name = "addressAndungibleAssetValues", + Name = "addressAndFungibleAssetValues", Description = "Array of balance address and currency ticker and quantity to send." }, new QueryArgument diff --git a/NineChronicles.Headless/GraphTypes/FungibleIdAndCountInputType.cs b/NineChronicles.Headless/GraphTypes/FungibleIdAndCountInputType.cs index 5baa5dccd..b2dff8b8a 100644 --- a/NineChronicles.Headless/GraphTypes/FungibleIdAndCountInputType.cs +++ b/NineChronicles.Headless/GraphTypes/FungibleIdAndCountInputType.cs @@ -6,7 +6,7 @@ namespace NineChronicles.Headless.GraphTypes.Input { public class FungibleIdAndCountInputType : - InputObjectGraphType<(HashDigest fungibleId, uint count)> + InputObjectGraphType<(HashDigest fungibleId, int count)> { public FungibleIdAndCountInputType() { @@ -16,7 +16,7 @@ public FungibleIdAndCountInputType() name: "fungibleId", description: "Fungible ID"); - Field( + Field( name: "count", description: "Count"); } @@ -24,7 +24,7 @@ public FungibleIdAndCountInputType() public override object ParseDictionary(IDictionary value) { var fungibleId = (HashDigest)value["fungibleId"]!; - var count = (uint)value["count"]!; + var count = (int)value["count"]!; return (fungibleId, count); } } From 70532599dccc4194cc3597c44e897e5f6e442181 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Fri, 16 Jun 2023 21:04:23 +0900 Subject: [PATCH 16/89] Bump lib9c:2023q3-iap --- Lib9c | 2 +- .../ActionContextMarshaller.cs | 5 --- .../Commands/StateCommand.cs | 1 - .../GraphTypes/ActionQueryTest.cs | 20 ++++++++-- .../GraphTypes/GraphQLTestBase.cs | 1 - .../GraphTypes/StandaloneMutationTest.cs | 1 - .../GraphTypes/StandaloneQueryTest.cs | 1 - .../TransactionHeadlessQueryTest.cs | 1 - .../GraphTypes/ActionQueryFields/Garages.cs | 39 ++++++++++++++----- .../GraphTypes/SimplifyCurrencyInputType.cs | 9 +++-- .../GraphTypes/StateQuery.cs | 4 +- 11 files changed, 55 insertions(+), 29 deletions(-) diff --git a/Lib9c b/Lib9c index 8a427fc81..54f7f9c53 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 8a427fc81e3b3e61c88d1faa53719ee7ddb9f8b0 +Subproject commit 54f7f9c5312e15ffde6b571cbda5aef4cbc09530 diff --git a/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionContextMarshaller.cs b/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionContextMarshaller.cs index 0bcf97fbc..39b14ba3c 100644 --- a/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionContextMarshaller.cs +++ b/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionContextMarshaller.cs @@ -33,11 +33,6 @@ public static Dictionary Marshal(this IActionContext actionContext) dictionary = dictionary.Add("tx_id", txId.ByteArray); } - if (actionContext.PreviousStateRootHash is { } previousStateRootHash) - { - dictionary = dictionary.Add("previous_state_root_hash", previousStateRootHash.ByteArray); - } - return dictionary; } diff --git a/NineChronicles.Headless.Executable/Commands/StateCommand.cs b/NineChronicles.Headless.Executable/Commands/StateCommand.cs index e33bb298a..9fb92ec7f 100644 --- a/NineChronicles.Headless.Executable/Commands/StateCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/StateCommand.cs @@ -137,7 +137,6 @@ IStateStore stateStore ? BlockChain.DetermineGenesisStateRootHash( actionEvaluator, preEvalBlock, - policy.BlockAction, out delta) : chain.DetermineBlockStateRootHash( preEvalBlock, diff --git a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs index 8ca8e8500..f12f8767f 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs @@ -954,14 +954,17 @@ public async Task LoadIntoMyGarages() }; var fungibleIdAndCountsString = string.Join(",", fungibleIdAndCounts.Select(tuple => $"{{ fungibleId: {{ value: \"{tuple.fungibleId.ToString()}\"}}, count: {tuple.count} }}")); + var memo = "memo"; var expectedAction = new LoadIntoMyGarages( fungibleAssetValues, inventoryAddr, - fungibleIdAndCounts); + fungibleIdAndCounts, + memo); var query = "{ loadIntoMyGarages(args: { " + $"fungibleAssetValues: [{fungibleAssetValuesString}], " + $"inventoryAddr: \"{inventoryAddr.ToHex()}\", " + - $"fungibleIdAndCounts: [{fungibleIdAndCountsString}]" + + $"fungibleIdAndCounts: [{fungibleIdAndCountsString}]," + + $"memo: \"{memo}\"" + "}) }"; var queryResult = await ExecuteQueryAsync( query, @@ -976,6 +979,7 @@ public async Task LoadIntoMyGarages() Assert.True(expectedAction.FungibleAssetValues.SequenceEqual(action.FungibleAssetValues)); Assert.Equal(expectedAction.InventoryAddr, action.InventoryAddr); Assert.True(expectedAction.FungibleIdAndCounts.SequenceEqual(action.FungibleIdAndCounts)); + Assert.Equal(expectedAction.Memo, action.Memo); } [Fact] @@ -998,14 +1002,17 @@ public async Task DeliverToOthersGarages() }; var fungibleIdAndCountsString = string.Join(",", fungibleIdAndCounts.Select(tuple => $"{{ fungibleId: {{ value: \"{tuple.fungibleId.ToString()}\"}}, count: {tuple.count} }}")); + var memo = "memo"; var expectedAction = new DeliverToOthersGarages( recipientAgentAddr, fungibleAssetValues, - fungibleIdAndCounts); + fungibleIdAndCounts, + memo); var query = "{ deliverToOthersGarages(args: { " + $"recipientAgentAddr: \"{recipientAgentAddr.ToHex()}\", " + $"fungibleAssetValues: [{fungibleAssetValuesString}], " + $"fungibleIdAndCounts: [{fungibleIdAndCountsString}] " + + $"memo: \"{memo}\"" + "}) }"; var queryResult = await ExecuteQueryAsync( query, @@ -1020,6 +1027,7 @@ public async Task DeliverToOthersGarages() Assert.Equal(expectedAction.RecipientAgentAddr, action.RecipientAgentAddr); Assert.True(expectedAction.FungibleAssetValues.SequenceEqual(action.FungibleAssetValues)); Assert.True(expectedAction.FungibleIdAndCounts.SequenceEqual(action.FungibleIdAndCounts)); + Assert.Equal(expectedAction.Memo, action.Memo); } [Fact] @@ -1043,14 +1051,17 @@ public async Task UnloadFromMyGarages() }; var fungibleIdAndCountsString = string.Join(",", fungibleIdAndCounts.Select(tuple => $"{{ fungibleId: {{ value: \"{tuple.fungibleId.ToString()}\"}}, count: {tuple.count} }}")); + var memo = "memo"; var expectedAction = new LoadIntoMyGarages( fungibleAssetValues, inventoryAddr, - fungibleIdAndCounts); + fungibleIdAndCounts, + memo); var query = "{ unloadFromMyGarages(args: { " + $"fungibleAssetValues: [{fungibleAssetValuesString}], " + $"inventoryAddr: \"{inventoryAddr.ToHex()}\", " + $"fungibleIdAndCounts: [{fungibleIdAndCountsString}]" + + $"memo: \"{memo}\"" + "}) }"; var queryResult = await ExecuteQueryAsync( query, @@ -1065,6 +1076,7 @@ public async Task UnloadFromMyGarages() Assert.True(expectedAction.FungibleAssetValues.SequenceEqual(action.FungibleAssetValues)); Assert.Equal(expectedAction.InventoryAddr, action.InventoryAddr); Assert.True(expectedAction.FungibleIdAndCounts.SequenceEqual(action.FungibleIdAndCounts)); + Assert.Equal(expectedAction.Memo, action.Memo); } } } diff --git a/NineChronicles.Headless.Tests/GraphTypes/GraphQLTestBase.cs b/NineChronicles.Headless.Tests/GraphTypes/GraphQLTestBase.cs index 5bbd6264f..e97fc7989 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/GraphQLTestBase.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/GraphQLTestBase.cs @@ -90,7 +90,6 @@ public GraphQLTestBase(ITestOutputHelper output) .ToList()), states: ImmutableDictionary.Create()) }.Select((sa, nonce) => Transaction.Create(nonce + 1, AdminPrivateKey, null, new[] { sa }))), - blockAction: blockAction, privateKey: AdminPrivateKey); var ncService = ServiceBuilder.CreateNineChroniclesNodeService(genesisBlock, ProposerPrivateKey); diff --git a/NineChronicles.Headless.Tests/GraphTypes/StandaloneMutationTest.cs b/NineChronicles.Headless.Tests/GraphTypes/StandaloneMutationTest.cs index 26a7994de..968b03ac0 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/StandaloneMutationTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/StandaloneMutationTest.cs @@ -1017,7 +1017,6 @@ private Block MakeGenesisBlock( .ToList()), states: ImmutableDictionary.Create()) }.Select((sa, nonce) => Transaction.Create(nonce + 1, AdminPrivateKey, null, new[] { sa }))), - blockAction: ServiceBuilder.BlockPolicy.BlockAction, privateKey: AdminPrivateKey); } } diff --git a/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.cs index 24d836c9b..a73faeca3 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.cs @@ -1154,7 +1154,6 @@ private NineChroniclesNodeService MakeNineChroniclesNodeService(PrivateKey priva Transaction.Create(nonce + 1, ProposerPrivateKey, null, new[] { sa })) ), - blockAction: blockPolicy.BlockAction, privateKey: ProposerPrivateKey ); diff --git a/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs index 94487e4dd..21dc8d752 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs @@ -58,7 +58,6 @@ public TransactionHeadlessQueryTest() states: ImmutableDictionary.Create()) }.Select((sa, nonce) => Transaction.Create(nonce, new PrivateKey(), null, new[] { sa })) .ToImmutableList(), - blockAction: policy.BlockAction, privateKey: new PrivateKey() ); _blockChain = BlockChain.Create( diff --git a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs index 87be4c76e..72a086a3b 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs @@ -57,7 +57,12 @@ private void RegisterGarages() new QueryArgument>> { Name = "fungibleIdAndCounts", - Description = "Array of fungible ID and count" + Description = "Array of fungible ID and count", + }, + new QueryArgument + { + Name = "memo", + Description = "Memo", } ), resolve: context => @@ -80,11 +85,13 @@ private void RegisterGarages() var fungibleIdAndCounts = context.GetArgument fungibleId, int count)>?>( "fungibleIdAndCounts"); + var memo = context.GetArgument("memo"); ActionBase action = new LoadIntoMyGarages( fungibleAssetValues, inventoryAddr, - fungibleIdAndCounts); + fungibleIdAndCounts, + memo); return Encode(context, action); } ); @@ -100,12 +107,17 @@ private void RegisterGarages() new QueryArgument>> { Name = "fungibleAssetValues", - Description = "Array of currency ticket and quantity to deliver." + Description = "Array of currency ticket and quantity to deliver.", }, new QueryArgument>> { Name = "fungibleIdAndCounts", - Description = "Array of Fungible ID and count to deliver." + Description = "Array of Fungible ID and count to deliver.", + }, + new QueryArgument + { + Name = "memo", + Description = "Memo", } ), resolve: context => @@ -123,11 +135,13 @@ private void RegisterGarages() var fungibleIdAndCounts = context.GetArgument fungibleId, int count)>?>( "fungibleIdAndCounts"); + var memo = context.GetArgument("memo"); ActionBase action = new DeliverToOthersGarages( recipientAgentAddr, fungibleAssetValues, - fungibleIdAndCounts); + fungibleIdAndCounts, + memo); return Encode(context, action); } ); @@ -138,17 +152,22 @@ private void RegisterGarages() new QueryArgument>> { Name = "addressAndFungibleAssetValues", - Description = "Array of balance address and currency ticker and quantity to send." + Description = "Array of balance address and currency ticker and quantity to send.", }, new QueryArgument { Name = "inventoryAddr", - Description = "Inventory address to receive items." + Description = "Inventory address to receive items.", }, new QueryArgument>> { Name = "fungibleIdAndCounts", - Description = "Array of fungible ID and count to send." + Description = "Array of fungible ID and count to send.", + }, + new QueryArgument + { + Name = "memo", + Description = "Memo", } ), resolve: context => @@ -168,11 +187,13 @@ private void RegisterGarages() var fungibleIdAndCounts = context.GetArgument fungibleId, int count)>?>( "fungibleIdAndCounts"); + var memo = context.GetArgument("memo"); ActionBase action = new UnloadFromMyGarages( fungibleAssetValues, inventoryAddr, - fungibleIdAndCounts); + fungibleIdAndCounts, + memo); return Encode(context, action); } ); diff --git a/NineChronicles.Headless/GraphTypes/SimplifyCurrencyInputType.cs b/NineChronicles.Headless/GraphTypes/SimplifyCurrencyInputType.cs index f1365b543..a3f245a4c 100644 --- a/NineChronicles.Headless/GraphTypes/SimplifyCurrencyInputType.cs +++ b/NineChronicles.Headless/GraphTypes/SimplifyCurrencyInputType.cs @@ -34,10 +34,13 @@ public SimplifyCurrencyInputType() public override object ParseDictionary(IDictionary value) { var ticker = (string)value["ticker"]!; - var currency = Currencies.GetCurrency(ticker); - if (currency is not null) + try { - return currency; + return Currencies.GetMinterlessCurrency(ticker); + } + catch + { + // ignore. } if (ticker == "NCG") diff --git a/NineChronicles.Headless/GraphTypes/StateQuery.cs b/NineChronicles.Headless/GraphTypes/StateQuery.cs index 180cfe51c..dbcc1750f 100644 --- a/NineChronicles.Headless/GraphTypes/StateQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StateQuery.cs @@ -283,9 +283,9 @@ public StateQuery() if (context.Source.BlockIndex < StakeState.StakeRewardSheetV2Index) { stakeRegularRewardSheet = new StakeRegularRewardSheet(); - stakeRegularRewardSheet.Set(ClaimStakeReward.StakeRegularRewardSheetV1Data); + stakeRegularRewardSheet.Set(ClaimStakeReward.V1.StakeRegularRewardSheetCsv); stakeRegularFixedRewardSheet = new StakeRegularFixedRewardSheet(); - stakeRegularFixedRewardSheet.Set(ClaimStakeReward.StakeRegularFixedRewardSheetV1Data); + stakeRegularFixedRewardSheet.Set(ClaimStakeReward.V1.StakeRegularFixedRewardSheetCsv); } else { From 82a047e33b119ed1c9b81e3c01bc01c53b17847a Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Mon, 19 Jun 2023 21:57:36 +0900 Subject: [PATCH 17/89] Implement GetCurrency and GetFungibleAssetValue methods to StandaloneContext --- ...nicles.Headless.Executable.sln.DotSettings | 1 + .../GraphQLTestUtils.cs | 5 + NineChronicles.Headless/StandaloneContext.cs | 92 +++++++++++++++++++ 3 files changed, 98 insertions(+) diff --git a/NineChronicles.Headless.Executable.sln.DotSettings b/NineChronicles.Headless.Executable.sln.DotSettings index 52e48e018..61ddddd89 100644 --- a/NineChronicles.Headless.Executable.sln.DotSettings +++ b/NineChronicles.Headless.Executable.sln.DotSettings @@ -1,2 +1,3 @@  + NCG True \ No newline at end of file diff --git a/NineChronicles.Headless.Tests/GraphQLTestUtils.cs b/NineChronicles.Headless.Tests/GraphQLTestUtils.cs index 3d263e6ac..8c5c6fab5 100644 --- a/NineChronicles.Headless.Tests/GraphQLTestUtils.cs +++ b/NineChronicles.Headless.Tests/GraphQLTestUtils.cs @@ -14,8 +14,10 @@ using Libplanet.Store.Trie; using Libplanet.Tx; using Microsoft.Extensions.DependencyInjection; +using Nekoyume; using Nekoyume.Action; using Nekoyume.Action.Loader; +using Nekoyume.Model.State; namespace NineChronicles.Headless.Tests { @@ -125,10 +127,13 @@ PrivateKey minerPrivateKey stateStore, genesisBlock, actionEvaluator); + var ncg = new GoldCurrencyState((Dictionary)blockchain.GetState(Addresses.GoldCurrency)) + .Currency; return new StandaloneContext { BlockChain = blockchain, Store = store, + NCG = ncg, }; } } diff --git a/NineChronicles.Headless/StandaloneContext.cs b/NineChronicles.Headless/StandaloneContext.cs index 06f7f71a1..6dfd69afc 100644 --- a/NineChronicles.Headless/StandaloneContext.cs +++ b/NineChronicles.Headless/StandaloneContext.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Concurrent; +using System.Numerics; using System.Reactive.Subjects; +using Bencodex.Types; +using Lib9c; using Libplanet; using Libplanet.Assets; using Libplanet.Blockchain; @@ -8,6 +11,7 @@ using Libplanet.Net; using Libplanet.Headless; using Libplanet.Store; +using Nekoyume; using Nekoyume.Model.State; using NineChronicles.Headless.GraphTypes; @@ -45,6 +49,8 @@ public class StandaloneContext public Swarm? Swarm { get; internal set; } + public Currency? NCG { get; internal set; } + internal TimeSpan DifferentAppProtocolVersionEncounterInterval { get; set; } = TimeSpan.FromSeconds(30); internal TimeSpan NotificationInterval { get; set; } = TimeSpan.FromSeconds(30); @@ -54,5 +60,91 @@ public class StandaloneContext internal TimeSpan MonsterCollectionStateInterval { get; set; } = TimeSpan.FromSeconds(30); internal TimeSpan MonsterCollectionStatusInterval { get; set; } = TimeSpan.FromSeconds(30); + + public bool TryGetCurrency(CurrencyEnum currencyEnum, out Currency? currency) + { + return TryGetCurrency(currencyEnum.ToString(), out currency); + } + + public bool TryGetCurrency(string ticker, out Currency? currency) + { + currency = ticker switch + { + "NCG" => GetNCG(), + _ => Currencies.GetMinterlessCurrency(ticker), + }; + return currency is not null; + } + + private Currency? GetNCG() + { + if (NCG is not null) + { + return NCG; + } + + if (BlockChain?.GetState(Addresses.GoldCurrency) is + Dictionary goldCurrencyDict) + { + var goldCurrency = new GoldCurrencyState(goldCurrencyDict); + NCG = goldCurrency.Currency; + } + + return NCG; + } + + public bool TryGetFungibleAssetValue( + CurrencyEnum currencyEnum, + BigInteger majorUnit, + BigInteger minorUnit, + out FungibleAssetValue? fungibleAssetValue) + { + return TryGetFungibleAssetValue( + currencyEnum.ToString(), + majorUnit, + minorUnit, + out fungibleAssetValue); + } + + public bool TryGetFungibleAssetValue( + string ticker, + BigInteger majorUnit, + BigInteger minorUnit, + out FungibleAssetValue? fungibleAssetValue) + { + if (TryGetCurrency(ticker, out var currency)) + { + fungibleAssetValue = new FungibleAssetValue(currency!.Value, majorUnit, minorUnit); + return true; + } + + fungibleAssetValue = null; + return false; + } + + public bool TryGetFungibleAssetValue( + CurrencyEnum currencyEnum, + string value, + out FungibleAssetValue? fungibleAssetValue) + { + return TryGetFungibleAssetValue( + currencyEnum.ToString(), + value, + out fungibleAssetValue); + } + + public bool TryGetFungibleAssetValue( + string ticker, + string value, + out FungibleAssetValue? fungibleAssetValue) + { + if (TryGetCurrency(ticker, out var currency)) + { + fungibleAssetValue = FungibleAssetValue.Parse(currency!.Value, value); + } + + fungibleAssetValue = null; + return false; + } } } From 21afaf0806725d51b60d5aba0d8380b57a730310 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Mon, 19 Jun 2023 22:00:55 +0900 Subject: [PATCH 18/89] Rename to pascal case of ActionQuery.StandaloneContext --- NineChronicles.Headless/GraphTypes/ActionQuery.cs | 6 +++--- .../GraphTypes/ActionQueryFields/HackAndSlash.cs | 2 +- NineChronicles.Headless/GraphTypes/ActionTxQuery.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/NineChronicles.Headless/GraphTypes/ActionQuery.cs b/NineChronicles.Headless/GraphTypes/ActionQuery.cs index 49805d1bd..059924a4e 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQuery.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQuery.cs @@ -21,12 +21,12 @@ namespace NineChronicles.Headless.GraphTypes { public partial class ActionQuery : ObjectGraphType { - private static readonly Codec Codec = new Codec(); - internal StandaloneContext standaloneContext { get; set; } + private static readonly Codec Codec = new(); + internal StandaloneContext StandaloneContext { get; set; } public ActionQuery(StandaloneContext standaloneContext) { - this.standaloneContext = standaloneContext; + StandaloneContext = standaloneContext; Field( name: "stake", diff --git a/NineChronicles.Headless/GraphTypes/ActionQueryFields/HackAndSlash.cs b/NineChronicles.Headless/GraphTypes/ActionQueryFields/HackAndSlash.cs index 921baa906..959f0127c 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQueryFields/HackAndSlash.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQueryFields/HackAndSlash.cs @@ -71,7 +71,7 @@ private void RegisterHackAndSlash() context.GetArgument?>("runeSlotInfos") ?? new List(); int? stageBuffId = context.GetArgument("stageBuffId"); - if (!(standaloneContext.BlockChain is { } chain)) + if (!(StandaloneContext.BlockChain is { } chain)) { throw new InvalidOperationException("BlockChain not found in the context"); } diff --git a/NineChronicles.Headless/GraphTypes/ActionTxQuery.cs b/NineChronicles.Headless/GraphTypes/ActionTxQuery.cs index a36493f07..6cdc02f95 100644 --- a/NineChronicles.Headless/GraphTypes/ActionTxQuery.cs +++ b/NineChronicles.Headless/GraphTypes/ActionTxQuery.cs @@ -18,10 +18,10 @@ public ActionTxQuery(StandaloneContext standaloneContext) : base(standaloneConte internal override byte[] Encode(IResolveFieldContext context, ActionBase action) { var publicKey = new PublicKey(ByteUtil.ParseHex(context.Parent!.GetArgument("publicKey"))); - if (!(standaloneContext.BlockChain is BlockChain blockChain)) + if (!(StandaloneContext.BlockChain is BlockChain blockChain)) { throw new ExecutionError( - $"{nameof(StandaloneContext)}.{nameof(StandaloneContext.BlockChain)} was not set yet!"); + $"{nameof(Headless.StandaloneContext)}.{nameof(Headless.StandaloneContext.BlockChain)} was not set yet!"); } Address signer = publicKey.ToAddress(); From 76fbb8c537ce2eaea0901db204e3dc0cb84de9ca Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Mon, 19 Jun 2023 22:01:46 +0900 Subject: [PATCH 19/89] Add CurrencyEnum.GARAGE type --- NineChronicles.Headless/GraphTypes/CurrencyEnumType.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/NineChronicles.Headless/GraphTypes/CurrencyEnumType.cs b/NineChronicles.Headless/GraphTypes/CurrencyEnumType.cs index 878185771..a3f91ac4b 100644 --- a/NineChronicles.Headless/GraphTypes/CurrencyEnumType.cs +++ b/NineChronicles.Headless/GraphTypes/CurrencyEnumType.cs @@ -8,6 +8,7 @@ public enum CurrencyEnum { CRYSTAL, NCG, + GARAGE, } public class CurrencyEnumType : EnumerationGraphType From a08c03f78ee64ee2d005b2a81b7a60a2067dda1a Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Mon, 19 Jun 2023 22:03:18 +0900 Subject: [PATCH 20/89] Improve SimplifyFungibleAssetValueInputType --- .../GarageFungibleAssetValueInputType.cs | 35 ------------ .../GraphTypes/SimplifyCurrencyInputType.cs | 54 ------------------- .../SimplifyFungibleAssetValueInputType.cs | 53 ++++++++++++------ 3 files changed, 38 insertions(+), 104 deletions(-) delete mode 100644 NineChronicles.Headless/GraphTypes/GarageFungibleAssetValueInputType.cs delete mode 100644 NineChronicles.Headless/GraphTypes/SimplifyCurrencyInputType.cs diff --git a/NineChronicles.Headless/GraphTypes/GarageFungibleAssetValueInputType.cs b/NineChronicles.Headless/GraphTypes/GarageFungibleAssetValueInputType.cs deleted file mode 100644 index 5e4e299e9..000000000 --- a/NineChronicles.Headless/GraphTypes/GarageFungibleAssetValueInputType.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Collections.Generic; -using System.Numerics; -using GraphQL.Types; - -namespace NineChronicles.Headless.GraphTypes -{ - public class GarageFungibleAssetValueInputType : - InputObjectGraphType<(CurrencyEnum currency, BigInteger majorUnit, BigInteger minorUnit)> - { - public GarageFungibleAssetValueInputType() - { - Name = "GarageFungibleAssetValueInput"; - - Field( - name: "currency", - description: "A currency type to be loaded."); - - Field>( - name: "majorUnit", - description: "Major unit of currency quantity."); - - Field>( - name: "minorUnit", - description: "Minor unit of currency quantity."); - } - - public override object ParseDictionary(IDictionary value) - { - var currency = (CurrencyEnum)value["currency"]!; - var majorUnit = (BigInteger)value["majorUnit"]!; - var minorUnit = (BigInteger)value["minorUnit"]!; - return (currency, majorUnit, minorUnit); - } - } -} diff --git a/NineChronicles.Headless/GraphTypes/SimplifyCurrencyInputType.cs b/NineChronicles.Headless/GraphTypes/SimplifyCurrencyInputType.cs deleted file mode 100644 index a3f245a4c..000000000 --- a/NineChronicles.Headless/GraphTypes/SimplifyCurrencyInputType.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Collections.Generic; -using GraphQL; -using GraphQL.Types; -using Lib9c; -using Libplanet; -using Libplanet.Assets; - -namespace NineChronicles.Headless.GraphTypes -{ - // FIXME: To StringGraphType. - public class SimplifyCurrencyInputType : InputObjectGraphType - { - /// - /// This NCG definition is for mainnet. - /// -#pragma warning disable CS0618 - public static readonly Currency NCG = Currency.Legacy( - "NCG", - 2, - new Address("0x47D082a115c63E7b58B1532d20E631538eaFADde")); -#pragma warning restore CS0618 - - public SimplifyCurrencyInputType() - { - Name = "SimplifyCurrencyInput"; - Description = "Generate currency from ticker. If ticker is NCG, it will return mainnet NCG.\n" + - " See also: Currency? Currencies.GetCurrency(string ticker) " + - "https://github.com/planetarium/lib9c/blob/main/Lib9c/Currencies.cs"; - Field>( - name: "ticker", - description: "Ticker."); - } - - public override object ParseDictionary(IDictionary value) - { - var ticker = (string)value["ticker"]!; - try - { - return Currencies.GetMinterlessCurrency(ticker); - } - catch - { - // ignore. - } - - if (ticker == "NCG") - { - return NCG; - } - - throw new ExecutionError($"Invalid ticker: {ticker}"); - } - } -} diff --git a/NineChronicles.Headless/GraphTypes/SimplifyFungibleAssetValueInputType.cs b/NineChronicles.Headless/GraphTypes/SimplifyFungibleAssetValueInputType.cs index 6bd4381ce..51310f820 100644 --- a/NineChronicles.Headless/GraphTypes/SimplifyFungibleAssetValueInputType.cs +++ b/NineChronicles.Headless/GraphTypes/SimplifyFungibleAssetValueInputType.cs @@ -1,31 +1,54 @@ using System.Collections.Generic; -using System.Numerics; +using GraphQL; using GraphQL.Types; -using Libplanet.Assets; namespace NineChronicles.Headless.GraphTypes { - public class SimplifyFungibleAssetValueInputType : InputObjectGraphType + public class SimplifyFungibleAssetValueInputType : + InputObjectGraphType<(string currencyTicker, string value)> { public SimplifyFungibleAssetValueInputType() { Name = "SimplifyFungibleAssetValueInput"; - Field>( - name: "currency"); - Field>( - name: "majorUnit", - description: "Major unit of currency quantity."); - Field>( - name: "minorUnit", - description: "Minor unit of currency quantity."); + Description = "A fungible asset value ticker and amount." + + "You can specify either currencyEnum or currencyTicker."; + + Field( + name: "currencyEnum", + description: "A currency type to be loaded."); + + Field( + name: "currencyTicker", + description: "A currency ticker to be loaded."); + + Field>( + name: "value", + description: "A numeric string to parse. Can consist of digits, " + + "plus (+), minus (-), and decimal separator (.)." + + " "); } public override object ParseDictionary(IDictionary value) { - var currency = (Currency)value["currency"]!; - var majorUnit = (BigInteger)value["majorUnit"]!; - var minorUnit = (BigInteger)value["minorUnit"]!; - return new FungibleAssetValue(currency, majorUnit, minorUnit); + var value2 = (string)value["value"]!; + if (value.TryGetValue("currencyEnum", out var currencyEnum)) + { + if (value.ContainsKey("currencyTicker")) + { + throw new ExecutionError("currencyEnum and currencyTicker cannot be specified at the same time."); + } + + var currencyTicker = ((CurrencyEnum)currencyEnum!).ToString(); + return (currencyTicker, value: value2); + } + else if (value.TryGetValue("currencyTicker", out var currencyTicker)) + { + return ((string)currencyTicker!, value: value2); + } + else + { + throw new ExecutionError("currencyEnum or currencyTicker must be specified."); + } } } } From 4d4e22c791b4dc01cef872e584d6fe2024673227 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Mon, 19 Jun 2023 22:03:56 +0900 Subject: [PATCH 21/89] Improve BalanceInputType --- ...tValueInputType.cs => BalanceInputType.cs} | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) rename NineChronicles.Headless/GraphTypes/{GarageAddressAndFungibleAssetValueInputType.cs => BalanceInputType.cs} (53%) diff --git a/NineChronicles.Headless/GraphTypes/GarageAddressAndFungibleAssetValueInputType.cs b/NineChronicles.Headless/GraphTypes/BalanceInputType.cs similarity index 53% rename from NineChronicles.Headless/GraphTypes/GarageAddressAndFungibleAssetValueInputType.cs rename to NineChronicles.Headless/GraphTypes/BalanceInputType.cs index 9f4c8e116..fd1a292f4 100644 --- a/NineChronicles.Headless/GraphTypes/GarageAddressAndFungibleAssetValueInputType.cs +++ b/NineChronicles.Headless/GraphTypes/BalanceInputType.cs @@ -1,26 +1,27 @@ +using System; using System.Collections.Generic; using System.Numerics; using GraphQL.Types; using Libplanet; -using Libplanet.Assets; using Libplanet.Explorer.GraphTypes; namespace NineChronicles.Headless.GraphTypes { - public class GarageAddressAndFungibleAssetValueInputType : - InputObjectGraphType<(Address balanceAddr, GarageFungibleAssetValueInputType fav)> + public class BalanceInputType : InputObjectGraphType<( + Address balanceAddr, + SimplifyFungibleAssetValueInputType valueTuple)> { - public GarageAddressAndFungibleAssetValueInputType() + public BalanceInputType() { - Name = "GarageAddressAndFungibleAssetValueInput"; + Name = "BalanceInput"; Field( name: "balanceAddr", description: "Balance Address." ); - Field( - name: "fungibleAssetValue", + Field( + name: "value", description: "Fungible asset value ticker and amount." ); } @@ -28,8 +29,8 @@ public GarageAddressAndFungibleAssetValueInputType() public override object ParseDictionary(IDictionary value) { var addr = (Address)value["balanceAddr"]!; - var fav = ((CurrencyEnum, BigInteger, BigInteger))value["fungibleAssetValue"]!; - return (addr, fav); + var favTuple = ((string currencyTicker, string value))value["value"]!; + return (addr, favTuple); } } } From 90944255f1bf8896058a40d6d6eb9f02a7c2f7a1 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Mon, 19 Jun 2023 22:05:39 +0900 Subject: [PATCH 22/89] Apply StandaloneContext.TryGetCurrency() method --- .../GraphTypes/ActionQuery.cs | 15 ++++++--------- .../GraphTypes/AddressQuery.cs | 17 +++++------------ 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/NineChronicles.Headless/GraphTypes/ActionQuery.cs b/NineChronicles.Headless/GraphTypes/ActionQuery.cs index 059924a4e..899ae8b13 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQuery.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQuery.cs @@ -7,7 +7,6 @@ using GraphQL; using GraphQL.Types; using Libplanet; -using Libplanet.Action; using Libplanet.Assets; using Libplanet.Explorer.GraphTypes; using Nekoyume.Action; @@ -188,15 +187,13 @@ public ActionQuery(StandaloneContext standaloneContext) { var sender = context.GetArgument
("sender"); var recipient = context.GetArgument
("recipient"); - Currency currency = context.GetArgument("currency") switch + var currencyEnum = context.GetArgument("currency"); + if (!standaloneContext.TryGetCurrency(currencyEnum, out var currency)) { - CurrencyEnum.NCG => new GoldCurrencyState( - (Dictionary)standaloneContext.BlockChain!.GetState(GoldCurrencyState.Address) - ).Currency, - CurrencyEnum.CRYSTAL => CrystalCalculator.CRYSTAL, - _ => throw new ExecutionError("Unsupported Currency type.") - }; - var amount = FungibleAssetValue.Parse(currency, context.GetArgument("amount")); + throw new ExecutionError($"Currency {currencyEnum} is not found."); + } + + var amount = FungibleAssetValue.Parse(currency!.Value, context.GetArgument("amount")); var memo = context.GetArgument("memo"); ActionBase action = new TransferAsset(sender, recipient, amount, memo); return Encode(context, action); diff --git a/NineChronicles.Headless/GraphTypes/AddressQuery.cs b/NineChronicles.Headless/GraphTypes/AddressQuery.cs index 85e07e9a2..0cc1732fd 100644 --- a/NineChronicles.Headless/GraphTypes/AddressQuery.cs +++ b/NineChronicles.Headless/GraphTypes/AddressQuery.cs @@ -90,20 +90,13 @@ public AddressQuery(StandaloneContext standaloneContext) }), resolve: context => { - var currency = context.GetArgument("currency"); - switch (currency) + var currencyEnum = context.GetArgument("currency"); + if (!standaloneContext.TryGetCurrency(currencyEnum, out var currency)) { - case CurrencyEnum.NCG: - var blockchain = standaloneContext.BlockChain!; - var goldCurrencyStateDict = - (Dictionary)blockchain.GetState(Addresses.GoldCurrency); - var goldCurrencyState = new GoldCurrencyState(goldCurrencyStateDict); - return goldCurrencyState.Currency.Minters; - case CurrencyEnum.CRYSTAL: - return CrystalCalculator.CRYSTAL.Minters; - default: - throw new SwitchExpressionException(currency); + throw new ExecutionError($"Currency {currencyEnum} is not found."); } + + return currency!.Value.Minters; }); } } From 5f6ddc06175d70d799e00965927f8aac4851bae5 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Mon, 19 Jun 2023 22:06:00 +0900 Subject: [PATCH 23/89] Remove useless types --- .../DeliverToOthersGaragesArgsInputType.cs | 55 ------------------ .../Garages/LoadIntoMyGaragesArgsInputType.cs | 56 ------------------- .../UnloadFromMyGaragesArgsInputType.cs | 56 ------------------- 3 files changed, 167 deletions(-) delete mode 100644 NineChronicles.Headless/GraphTypes/ActionArgs/Garages/DeliverToOthersGaragesArgsInputType.cs delete mode 100644 NineChronicles.Headless/GraphTypes/ActionArgs/Garages/LoadIntoMyGaragesArgsInputType.cs delete mode 100644 NineChronicles.Headless/GraphTypes/ActionArgs/Garages/UnloadFromMyGaragesArgsInputType.cs diff --git a/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/DeliverToOthersGaragesArgsInputType.cs b/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/DeliverToOthersGaragesArgsInputType.cs deleted file mode 100644 index b9923de2c..000000000 --- a/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/DeliverToOthersGaragesArgsInputType.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Security.Cryptography; -using GraphQL.Types; -using Libplanet; -using Libplanet.Assets; -using Libplanet.Explorer.GraphTypes; -using NineChronicles.Headless.GraphTypes.Input; - -namespace NineChronicles.Headless.GraphTypes.ActionArgs.Garages -{ - public class DeliverToOthersGaragesArgsInputType : InputObjectGraphType<( - Address recipientAgentAddr, - IEnumerable? fungibleAssetValues, - IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts)> - { - public DeliverToOthersGaragesArgsInputType() - { - Name = "DeliverToOthersGaragesArgsInput"; - - Field>( - name: "recipientAgentAddr", - description: "Recipient agent address."); - - Field>>( - name: "fungibleAssetValues", - description: "Array of currency ticker and quantity."); - - Field>>( - name: "fungibleIdAndCounts", - description: "Array of fungible ID and count."); - } - - public override object ParseDictionary(IDictionary value) - { - var recipientAgentAddr = (Address)value["recipientAgentAddr"]!; - IEnumerable? fungibleAssetValues = null; - if (value["fungibleAssetValues"] is object[] fungibleAssetValueObjects) - { - fungibleAssetValues = fungibleAssetValueObjects - .OfType(); - } - - IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts = null; - if (value["fungibleIdAndCounts"] is object[] fungibleIdAndCountObjects) - { - fungibleIdAndCounts = fungibleIdAndCountObjects - .OfType<(HashDigest, uint)>() - .Select(tuple => (tuple.Item1, (int)tuple.Item2)); - } - - return (recipientAgentAddr, fungibleAssetValues, fungibleIdAndCounts); - } - } -} diff --git a/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/LoadIntoMyGaragesArgsInputType.cs b/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/LoadIntoMyGaragesArgsInputType.cs deleted file mode 100644 index fb2953613..000000000 --- a/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/LoadIntoMyGaragesArgsInputType.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Security.Cryptography; -using GraphQL.Types; -using Libplanet; -using Libplanet.Assets; -using Libplanet.Explorer.GraphTypes; -using NineChronicles.Headless.GraphTypes.Input; - -namespace NineChronicles.Headless.GraphTypes.ActionArgs.Garages -{ - public class LoadIntoMyGaragesArgsInputType : InputObjectGraphType<( - IEnumerable<(Address balanceAddr, FungibleAssetValue value)>? fungibleAssetValues, - Address? inventoryAddr, - IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts)> - { - public LoadIntoMyGaragesArgsInputType() - { - Name = "LoadIntoMyGaragesArgsInput"; - - Field>>( - name: "fungibleAssetValues", - description: "Array of balance address and currency ticker and quantity."); - - Field( - name: "inventoryAddr", - description: "Inventory address"); - - Field>>( - name: "fungibleIdAndCounts", - description: "Array of fungible ID and count"); - } - - public override object ParseDictionary(IDictionary value) - { - IEnumerable<(Address balanceAddr, FungibleAssetValue value)>? fungibleAssetValues = null; - if (value["fungibleAssetValues"] is object[] fungibleAssetValueObjects) - { - fungibleAssetValues = fungibleAssetValueObjects - .OfType<(Address, FungibleAssetValue)>() - .Select(tuple => (tuple.Item1, tuple.Item2)); - } - - var inventoryAddr = (Address?)value["inventoryAddr"]; - IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts = null; - if (value["fungibleIdAndCounts"] is object[] fungibleIdAndCountObjects) - { - fungibleIdAndCounts = fungibleIdAndCountObjects - .OfType<(HashDigest, uint)>() - .Select(tuple => (tuple.Item1, (int)tuple.Item2)); - } - - return (fungibleAssetValues, inventoryAddr, fungibleIdAndCounts); - } - } -} diff --git a/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/UnloadFromMyGaragesArgsInputType.cs b/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/UnloadFromMyGaragesArgsInputType.cs deleted file mode 100644 index 2ce518890..000000000 --- a/NineChronicles.Headless/GraphTypes/ActionArgs/Garages/UnloadFromMyGaragesArgsInputType.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Security.Cryptography; -using GraphQL.Types; -using Libplanet; -using Libplanet.Assets; -using Libplanet.Explorer.GraphTypes; -using NineChronicles.Headless.GraphTypes.Input; - -namespace NineChronicles.Headless.GraphTypes.ActionArgs.Garages -{ - public class UnloadFromMyGaragesArgsInputType : InputObjectGraphType<( - IEnumerable<(Address balanceAddr, FungibleAssetValue value)>? fungibleAssetValues, - Address? inventoryAddr, - IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts)> - { - public UnloadFromMyGaragesArgsInputType() - { - Name = "UnloadFromMyGaragesArgsInput"; - - Field>>( - name: "fungibleAssetValues", - description: "Array of balance address and currency ticker and quantity."); - - Field( - name: "inventoryAddr", - description: "Inventory address"); - - Field>>( - name: "fungibleIdAndCounts", - description: "Array of fungible ID and count"); - } - - public override object ParseDictionary(IDictionary value) - { - IEnumerable<(Address balanceAddr, FungibleAssetValue value)>? fungibleAssetValues = null; - if (value["fungibleAssetValues"] is object[] fungibleAssetValueObjects) - { - fungibleAssetValues = fungibleAssetValueObjects - .OfType<(Address, FungibleAssetValue)>() - .Select(tuple => (tuple.Item1, tuple.Item2)); - } - - var inventoryAddr = (Address?)value["inventoryAddr"]; - IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts = null; - if (value["fungibleIdAndCounts"] is object[] fungibleIdAndCountObjects) - { - fungibleIdAndCounts = fungibleIdAndCountObjects - .OfType<(HashDigest, uint)>() - .Select(tuple => (tuple.Item1, (int)tuple.Item2)); - } - - return (fungibleAssetValues, inventoryAddr, fungibleIdAndCounts); - } - } -} From 8dcebd1bb5d6fcb10c122cd4ca2873df93dd6f5f Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Mon, 19 Jun 2023 22:06:26 +0900 Subject: [PATCH 24/89] Improve garage queries --- .../GraphTypes/ActionQueryTest.cs | 323 +++++++++++++----- .../GraphTypes/ActionQueryFields/Garages.cs | 130 +++---- 2 files changed, 298 insertions(+), 155 deletions(-) diff --git a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs index f12f8767f..4d08e27d3 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs @@ -13,6 +13,7 @@ using Libplanet; using Libplanet.Assets; using Libplanet.Crypto; +using Microsoft.Extensions.Primitives; using Nekoyume; using Nekoyume.Action; using Nekoyume.Action.Garages; @@ -933,138 +934,246 @@ public async Task CombinationConsumable() Assert.Equal(recipeId, action.recipeId); } - [Fact] - public async Task LoadIntoMyGarages() + [Theory] + [MemberData(nameof(GetMemberDataOfLoadIntoMyGarages))] + public async Task LoadIntoMyGarages( + IEnumerable<(Address balanceAddr, FungibleAssetValue value)>? fungibleAssetValues, + Address? inventoryAddr, + IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts, + string? memo) { - var fungibleAssetValues = new[] - { - (balanceAddr: new PrivateKey().ToAddress(), value: new FungibleAssetValue(Currencies.Crystal, 1, 0)), - (balanceAddr: new PrivateKey().ToAddress(), value: new FungibleAssetValue(Currencies.Crystal, 1, 0)), - }; - var fungibleAssetValuesString = string.Join(",", fungibleAssetValues.Select(tuple => - $"{{ balanceAddr: \"{tuple.balanceAddr.ToHex()}\", " + - $"value: {{ currency: {{ ticker: \"{tuple.value.Currency.Ticker}\" }}, " + - $"majorUnit: {tuple.value.MajorUnit}, " + - $"minorUnit: {tuple.value.MinorUnit} }} }}")); - var inventoryAddr = new PrivateKey().ToAddress(); - var fungibleIdAndCounts = new[] - { - (fungibleId: new HashDigest(), count: 1), - (fungibleId: new HashDigest(), count: 1), - }; - var fungibleIdAndCountsString = string.Join(",", fungibleIdAndCounts.Select(tuple => - $"{{ fungibleId: {{ value: \"{tuple.fungibleId.ToString()}\"}}, count: {tuple.count} }}")); - var memo = "memo"; var expectedAction = new LoadIntoMyGarages( fungibleAssetValues, inventoryAddr, fungibleIdAndCounts, memo); - var query = "{ loadIntoMyGarages(args: { " + - $"fungibleAssetValues: [{fungibleAssetValuesString}], " + - $"inventoryAddr: \"{inventoryAddr.ToHex()}\", " + - $"fungibleIdAndCounts: [{fungibleIdAndCountsString}]," + - $"memo: \"{memo}\"" + - "}) }"; + var sb = new StringBuilder("{ loadIntoMyGarages("); + if (fungibleAssetValues is not null) + { + sb.Append("fungibleAssetValues: ["); + sb.Append(string.Join(",", fungibleAssetValues.Select(tuple => + $"{{ balanceAddr: \"{tuple.balanceAddr.ToHex()}\", " + + $"value: {{ currencyTicker: \"{tuple.value.Currency.Ticker}\"," + + $"value: \"{tuple.value.GetQuantityString()}\" }} }}"))); + sb.Append("],"); + } + + if (inventoryAddr is not null) + { + sb.Append($"inventoryAddr: \"{inventoryAddr.Value.ToHex()}\","); + } + + if (fungibleIdAndCounts is not null) + { + sb.Append("fungibleIdAndCounts: ["); + sb.Append(string.Join(",", fungibleIdAndCounts.Select(tuple => + $"{{ fungibleId: {{ value: \"{tuple.fungibleId.ToString()}\" }}, " + + $"count: {tuple.count} }}"))); + sb.Append("],"); + } + + if (memo is not null) + { + sb.Append($"memo: \"{memo}\""); + } + + // Remove last ',' if exists. + if (sb[^1] == ',') + { + sb.Remove(sb.Length - 1, 1); + } + + sb.Append(") }"); var queryResult = await ExecuteQueryAsync( - query, + sb.ToString(), standaloneContext: _standaloneContext); Assert.Null(queryResult.Errors); - var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; var plainValue = _codec.Decode(ByteUtil.ParseHex((string)data["loadIntoMyGarages"])); Assert.IsType(plainValue); var actionBase = DeserializeNCAction(plainValue); - var action = Assert.IsType(actionBase); - Assert.True(expectedAction.FungibleAssetValues.SequenceEqual(action.FungibleAssetValues)); - Assert.Equal(expectedAction.InventoryAddr, action.InventoryAddr); - Assert.True(expectedAction.FungibleIdAndCounts.SequenceEqual(action.FungibleIdAndCounts)); - Assert.Equal(expectedAction.Memo, action.Memo); + var actualAction = Assert.IsType(actionBase); + Assert.True(expectedAction.FungibleAssetValues?.SequenceEqual(actualAction.FungibleAssetValues) ?? + actualAction.FungibleAssetValues is null); + Assert.Equal(expectedAction.InventoryAddr, actualAction.InventoryAddr); + Assert.True(expectedAction.FungibleIdAndCounts?.SequenceEqual(actualAction.FungibleIdAndCounts) ?? + actualAction.FungibleIdAndCounts is null); + Assert.Equal(expectedAction.Memo, actualAction.Memo); } - [Fact] - public async Task DeliverToOthersGarages() + private static IEnumerable GetMemberDataOfLoadIntoMyGarages() { - var recipientAgentAddr = new PrivateKey().ToAddress(); - var fungibleAssetValues = new[] + yield return new object[] { - new FungibleAssetValue(Currencies.Crystal, 1, 0), - new FungibleAssetValue(Currencies.Crystal, 1, 0), + null, + null, + null, + "memo", }; - var fungibleAssetValuesString = string.Join(",", fungibleAssetValues.Select(fav => - $"{{ currency: {{ ticker: \"{fav.Currency.Ticker}\" }}, " + - $"majorUnit: {fav.MajorUnit}, " + - $"minorUnit: {fav.MinorUnit} }}")); - var fungibleIdAndCounts = new[] - { - (fungibleId: new HashDigest(), count: 1), - (fungibleId: new HashDigest(), count: 1), + yield return new object[] + { + new[] + { + ( + address: new PrivateKey().ToAddress(), + fungibleAssetValue: new FungibleAssetValue(Currencies.Garage, 1, 0) + ), + ( + address: new PrivateKey().ToAddress(), + fungibleAssetValue: new FungibleAssetValue(Currencies.Garage, 1, 0) + ), + }, + new PrivateKey().ToAddress(), + new[] + { + (fungibleId: new HashDigest(), count: 1), + (fungibleId: new HashDigest(), count: 1), + }, + "memo", }; - var fungibleIdAndCountsString = string.Join(",", fungibleIdAndCounts.Select(tuple => - $"{{ fungibleId: {{ value: \"{tuple.fungibleId.ToString()}\"}}, count: {tuple.count} }}")); - var memo = "memo"; + } + + [Theory] + [MemberData(nameof(GetMemberDataOfDeliverToOthersGarages))] + public async Task DeliverToOthersGarages( + Address recipientAgentAddr, + IEnumerable? fungibleAssetValues, + IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts, + string? memo) + { var expectedAction = new DeliverToOthersGarages( recipientAgentAddr, fungibleAssetValues, fungibleIdAndCounts, memo); - var query = "{ deliverToOthersGarages(args: { " + - $"recipientAgentAddr: \"{recipientAgentAddr.ToHex()}\", " + - $"fungibleAssetValues: [{fungibleAssetValuesString}], " + - $"fungibleIdAndCounts: [{fungibleIdAndCountsString}] " + - $"memo: \"{memo}\"" + - "}) }"; + var sb = new StringBuilder("{ deliverToOthersGarages("); + sb.Append($"recipientAgentAddr: \"{recipientAgentAddr.ToHex()}\","); + if (fungibleAssetValues is not null) + { + sb.Append("fungibleAssetValues: ["); + sb.Append(string.Join(",", fungibleAssetValues.Select(tuple => + $"{{ currencyTicker: \"{tuple.Currency.Ticker}\", " + + $"value: \"{tuple.GetQuantityString()}\" }}"))); + sb.Append("],"); + } + + if (fungibleIdAndCounts is not null) + { + sb.Append("fungibleIdAndCounts: ["); + sb.Append(string.Join(",", fungibleIdAndCounts.Select(tuple => + $"{{ fungibleId: {{ value: \"{tuple.fungibleId.ToString()}\" }}, " + + $"count: {tuple.count} }}"))); + sb.Append("],"); + } + + if (memo is not null) + { + sb.Append($"memo: \"{memo}\""); + } + + // Remove last ',' if exists. + if (sb[^1] == ',') + { + sb.Remove(sb.Length - 1, 1); + } + + sb.Append(") }"); + var queryResult = await ExecuteQueryAsync( - query, + sb.ToString(), standaloneContext: _standaloneContext); Assert.Null(queryResult.Errors); - var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; var plainValue = _codec.Decode(ByteUtil.ParseHex((string)data["deliverToOthersGarages"])); Assert.IsType(plainValue); var actionBase = DeserializeNCAction(plainValue); var action = Assert.IsType(actionBase); Assert.Equal(expectedAction.RecipientAgentAddr, action.RecipientAgentAddr); - Assert.True(expectedAction.FungibleAssetValues.SequenceEqual(action.FungibleAssetValues)); - Assert.True(expectedAction.FungibleIdAndCounts.SequenceEqual(action.FungibleIdAndCounts)); + Assert.True(expectedAction.FungibleAssetValues?.SequenceEqual(action.FungibleAssetValues) ?? + action.FungibleAssetValues is null); + Assert.True(expectedAction.FungibleIdAndCounts?.SequenceEqual(action.FungibleIdAndCounts) ?? + action.FungibleIdAndCounts is null); Assert.Equal(expectedAction.Memo, action.Memo); } - - [Fact] - public async Task UnloadFromMyGarages() + + private static IEnumerable GetMemberDataOfDeliverToOthersGarages() { - var fungibleAssetValues = new[] + yield return new object[] { - (balanceAddr: new PrivateKey().ToAddress(), value: new FungibleAssetValue(Currencies.Crystal, 1, 0)), - (balanceAddr: new PrivateKey().ToAddress(), value: new FungibleAssetValue(Currencies.Crystal, 1, 0)), + new PrivateKey().ToAddress(), + null, + null, + null, }; - var fungibleAssetValuesString = string.Join(",", fungibleAssetValues.Select(tuple => - $"{{ balanceAddr: \"{tuple.balanceAddr.ToHex()}\", " + - $"value: {{ currency: {{ ticker: \"{tuple.value.Currency.Ticker}\" }}, " + - $"majorUnit: {tuple.value.MajorUnit}, " + - $"minorUnit: {tuple.value.MinorUnit} }} }}")); - var inventoryAddr = new PrivateKey().ToAddress(); - var fungibleIdAndCounts = new[] - { - (fungibleId: new HashDigest(), count: 1), - (fungibleId: new HashDigest(), count: 1), + yield return new object[] + { + new PrivateKey().ToAddress(), + new[] + { + new FungibleAssetValue(Currencies.Garage, 1, 0), + new FungibleAssetValue(Currencies.Garage, 1, 0), + }, + new[] + { + (fungibleId: new HashDigest(), count: 1), + (fungibleId: new HashDigest(), count: 1), + }, + "memo", }; - var fungibleIdAndCountsString = string.Join(",", fungibleIdAndCounts.Select(tuple => - $"{{ fungibleId: {{ value: \"{tuple.fungibleId.ToString()}\"}}, count: {tuple.count} }}")); - var memo = "memo"; - var expectedAction = new LoadIntoMyGarages( + } + + [Theory] + [MemberData(nameof(GetMemberDataOfUnloadFromMyGarages))] + public async Task UnloadFromMyGarages( + IEnumerable<(Address balanceAddr, FungibleAssetValue value)>? fungibleAssetValues, + Address? inventoryAddr, + IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts, + string? memo) + { + var expectedAction = new UnloadFromMyGarages( fungibleAssetValues, inventoryAddr, fungibleIdAndCounts, memo); - var query = "{ unloadFromMyGarages(args: { " + - $"fungibleAssetValues: [{fungibleAssetValuesString}], " + - $"inventoryAddr: \"{inventoryAddr.ToHex()}\", " + - $"fungibleIdAndCounts: [{fungibleIdAndCountsString}]" + - $"memo: \"{memo}\"" + - "}) }"; + var sb = new StringBuilder("{ unloadFromMyGarages("); + if (fungibleAssetValues is not null) + { + sb.Append("fungibleAssetValues: ["); + sb.Append(string.Join(",", fungibleAssetValues.Select(tuple => + $"{{ balanceAddr: \"{tuple.balanceAddr.ToHex()}\", " + + $"value: {{ currencyTicker: \"{tuple.value.Currency.Ticker}\"," + + $"value: \"{tuple.value.GetQuantityString()}\" }} }}"))); + sb.Append("],"); + } + + if (inventoryAddr is not null) + { + sb.Append($"inventoryAddr: \"{inventoryAddr.Value.ToHex()}\","); + } + + if (fungibleIdAndCounts is not null) + { + sb.Append("fungibleIdAndCounts: ["); + sb.Append(string.Join(",", fungibleIdAndCounts.Select(tuple => + $"{{ fungibleId: {{ value: \"{tuple.fungibleId.ToString()}\" }}, " + + $"count: {tuple.count} }}"))); + sb.Append("],"); + } + + if (memo is not null) + { + sb.Append($"memo: \"{memo}\""); + } + + // Remove last ',' if exists. + if (sb[^1] == ',') + { + sb.Remove(sb.Length - 1, 1); + } + + sb.Append(") }"); var queryResult = await ExecuteQueryAsync( - query, + sb.ToString(), standaloneContext: _standaloneContext); Assert.Null(queryResult.Errors); @@ -1073,10 +1182,44 @@ public async Task UnloadFromMyGarages() Assert.IsType(plainValue); var actionBase = DeserializeNCAction(plainValue); var action = Assert.IsType(actionBase); - Assert.True(expectedAction.FungibleAssetValues.SequenceEqual(action.FungibleAssetValues)); + Assert.True(expectedAction.FungibleAssetValues?.SequenceEqual(action.FungibleAssetValues) ?? + action.FungibleAssetValues is null); Assert.Equal(expectedAction.InventoryAddr, action.InventoryAddr); - Assert.True(expectedAction.FungibleIdAndCounts.SequenceEqual(action.FungibleIdAndCounts)); + Assert.True(expectedAction.FungibleIdAndCounts?.SequenceEqual(action.FungibleIdAndCounts) ?? + action.FungibleIdAndCounts is null); Assert.Equal(expectedAction.Memo, action.Memo); } + + private static IEnumerable GetMemberDataOfUnloadFromMyGarages() + { + yield return new object[] + { + null, + null, + null, + "memo", + }; + yield return new object[] + { + new[] + { + ( + address: new PrivateKey().ToAddress(), + fungibleAssetValue: new FungibleAssetValue(Currencies.Garage, 1, 0) + ), + ( + address: new PrivateKey().ToAddress(), + fungibleAssetValue: new FungibleAssetValue(Currencies.Garage, 1, 0) + ), + }, + new PrivateKey().ToAddress(), + new[] + { + (fungibleId: new HashDigest(), count: 1), + (fungibleId: new HashDigest(), count: 1), + }, + "memo", + }; + } } } diff --git a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs index 72a086a3b..777a4b291 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs @@ -1,7 +1,5 @@ using System.Collections.Generic; -using System.Numerics; using System.Security.Cryptography; -using Bencodex.Types; using GraphQL; using GraphQL.Types; using Libplanet; @@ -9,44 +7,20 @@ using Libplanet.Explorer.GraphTypes; using Nekoyume.Action; using Nekoyume.Action.Garages; -using Nekoyume.Helper; -using Nekoyume.Model.State; using NineChronicles.Headless.GraphTypes.Input; namespace NineChronicles.Headless.GraphTypes { public partial class ActionQuery { - private FungibleAssetValue getFungibleAssetValue( - CurrencyEnum curr, BigInteger majorUnit, BigInteger minorUnit) - { - Currency currency; - switch (curr) - { - case CurrencyEnum.NCG: - currency = new GoldCurrencyState( - (Dictionary)standaloneContext.BlockChain!.GetState(GoldCurrencyState.Address) - ).Currency; - break; - case CurrencyEnum.CRYSTAL: - currency = CrystalCalculator.CRYSTAL; - break; - default: - throw new ExecutionError($"Unsupported Currency type {curr}"); - } - - return new FungibleAssetValue(currency, majorUnit, minorUnit); - } - private void RegisterGarages() { Field>( "loadIntoMyGarages", arguments: new QueryArguments( - new QueryArgument>> + new QueryArgument>> { - Name = "addressAndFungibleAssetValues", + Name = "fungibleAssetValues", Description = "Array of balance address and currency ticker and quantity.", }, new QueryArgument @@ -67,24 +41,30 @@ private void RegisterGarages() ), resolve: context => { - // This transforms to (Address, FungibleAssetValue) and goes to.... - var addressAndFavList = context - .GetArgument>("addressAndFungibleAssetValues"); - // Here. and This is the input type of action. - var fungibleAssetValues = new List<(Address balanceAddr, FungibleAssetValue value)>(); - - foreach (var (addr, (curr, majorUnit, minorUnit)) in addressAndFavList) + var balanceInputList = context.GetArgument?>("fungibleAssetValues"); + List<(Address address, FungibleAssetValue fungibleAssetValue)>? fungibleAssetValues = null; + if (balanceInputList is not null) { - var fav = getFungibleAssetValue(curr, majorUnit, minorUnit); - fungibleAssetValues.Add((addr, fav)); + fungibleAssetValues = new List<(Address address, FungibleAssetValue fungibleAssetValue)>(); + foreach (var (balanceAddr, (currencyTicker, value)) in balanceInputList) + { + if (StandaloneContext.TryGetFungibleAssetValue(currencyTicker, value, out var fav)) + { + fungibleAssetValues.Add((balanceAddr, fav!.Value)); + } + else + { + throw new ExecutionError($"Invalid currency ticker: {currencyTicker}"); + } + } } var inventoryAddr = context.GetArgument("inventoryAddr"); - var fungibleIdAndCounts = - context.GetArgument fungibleId, int count)>?>( - "fungibleIdAndCounts"); + var fungibleIdAndCounts = context.GetArgument fungibleId, + int count)>?>("fungibleIdAndCounts"); var memo = context.GetArgument("memo"); ActionBase action = new LoadIntoMyGarages( @@ -104,7 +84,7 @@ private void RegisterGarages() Name = "recipientAgentAddr", Description = "Recipient agent address", }, - new QueryArgument>> + new QueryArgument>> { Name = "fungibleAssetValues", Description = "Array of currency ticket and quantity to deliver.", @@ -123,18 +103,29 @@ private void RegisterGarages() resolve: context => { var recipientAgentAddr = context.GetArgument
("recipientAgentAddr"); - var fungibleAssetValueList = context.GetArgument< - IEnumerable<(CurrencyEnum CurrencyEnum, BigInteger majorUnit, BigInteger minorUnit)> - >("fungibleAssetValues"); - var fungibleAssetValues = new List(); - foreach (var (curr, majorUnit, minorUnit) in fungibleAssetValueList) + var fungibleAssetValueInputList = context.GetArgument?>("fungibleAssetValues"); + List? fungibleAssetValues = null; + if (fungibleAssetValueInputList is not null) { - fungibleAssetValues.Add(getFungibleAssetValue(curr, majorUnit, minorUnit)); + fungibleAssetValues = new List(); + foreach (var (currencyTicker, value) in fungibleAssetValueInputList) + { + if (StandaloneContext.TryGetFungibleAssetValue(currencyTicker, value, out var fav)) + { + fungibleAssetValues.Add(fav!.Value); + } + else + { + throw new ExecutionError($"Invalid currency ticker: {currencyTicker}"); + } + } } - var fungibleIdAndCounts = - context.GetArgument fungibleId, int count)>?>( - "fungibleIdAndCounts"); + var fungibleIdAndCounts = context.GetArgument fungibleId, + int count)>?>("fungibleIdAndCounts"); var memo = context.GetArgument("memo"); ActionBase action = new DeliverToOthersGarages( @@ -149,9 +140,9 @@ private void RegisterGarages() Field>( "unloadFromMyGarages", arguments: new QueryArguments( - new QueryArgument>> + new QueryArgument>> { - Name = "addressAndFungibleAssetValues", + Name = "fungibleAssetValues", Description = "Array of balance address and currency ticker and quantity to send.", }, new QueryArgument @@ -172,21 +163,30 @@ private void RegisterGarages() ), resolve: context => { - var addressAndFavList = context - .GetArgument>("addressAndFungibleAssetValues"); - var fungibleAssetValues = new List<(Address balanceAddr, FungibleAssetValue value)>(); - foreach (var (addr, (curr, majorUnit, minorUnit)) in addressAndFavList) + var balanceInputList = context.GetArgument?>("fungibleAssetValues"); + List<(Address address, FungibleAssetValue fungibleAssetValue)>? fungibleAssetValues = null; + if (balanceInputList is not null) { - var fav = getFungibleAssetValue(curr, majorUnit, minorUnit); - fungibleAssetValues.Add((addr, fav)); + fungibleAssetValues = new List<(Address address, FungibleAssetValue fungibleAssetValue)>(); + foreach (var (addr, (currencyTicker, value)) in balanceInputList) + { + if (StandaloneContext.TryGetFungibleAssetValue(currencyTicker, value, out var fav)) + { + fungibleAssetValues.Add((addr, fav!.Value)); + } + else + { + throw new ExecutionError($"Invalid currency ticker: {currencyTicker}"); + } + } } var inventoryAddr = context.GetArgument("inventoryAddr"); - var fungibleIdAndCounts = - context.GetArgument fungibleId, int count)>?>( - "fungibleIdAndCounts"); + var fungibleIdAndCounts = context.GetArgument fungibleId, + int count)>?>("fungibleIdAndCounts"); var memo = context.GetArgument("memo"); ActionBase action = new UnloadFromMyGarages( From d065bc6dfee29b215ac36fa147b58a208a5ad45a Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Mon, 19 Jun 2023 22:06:34 +0900 Subject: [PATCH 25/89] Remove useless using --- NineChronicles.Headless/GraphTypes/StandaloneSchema.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/NineChronicles.Headless/GraphTypes/StandaloneSchema.cs b/NineChronicles.Headless/GraphTypes/StandaloneSchema.cs index e2b8631ed..4d05418df 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneSchema.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneSchema.cs @@ -1,6 +1,5 @@ using System; using GraphQL.Types; -using GraphQL.Utilities; using Microsoft.Extensions.DependencyInjection; namespace NineChronicles.Headless.GraphTypes From a5527c68a59f96fe529dd0617eac3e81b5cc1b86 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Mon, 19 Jun 2023 23:27:14 +0900 Subject: [PATCH 26/89] Remove HashDigestInputType --- .../GraphTypes/ActionQueryTest.cs | 6 ++--- .../GraphTypes/FungibleIdAndCountInputType.cs | 7 ++--- .../GraphTypes/HashDigestInputType.cs | 26 ------------------- 3 files changed, 7 insertions(+), 32 deletions(-) delete mode 100644 NineChronicles.Headless/GraphTypes/HashDigestInputType.cs diff --git a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs index 4d08e27d3..1a7e4ba69 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs @@ -967,7 +967,7 @@ public async Task LoadIntoMyGarages( { sb.Append("fungibleIdAndCounts: ["); sb.Append(string.Join(",", fungibleIdAndCounts.Select(tuple => - $"{{ fungibleId: {{ value: \"{tuple.fungibleId.ToString()}\" }}, " + + $"{{ fungibleId: \"{tuple.fungibleId.ToString()}\", " + $"count: {tuple.count} }}"))); sb.Append("],"); } @@ -1061,7 +1061,7 @@ public async Task DeliverToOthersGarages( { sb.Append("fungibleIdAndCounts: ["); sb.Append(string.Join(",", fungibleIdAndCounts.Select(tuple => - $"{{ fungibleId: {{ value: \"{tuple.fungibleId.ToString()}\" }}, " + + $"{{ fungibleId: \"{tuple.fungibleId.ToString()}\", " + $"count: {tuple.count} }}"))); sb.Append("],"); } @@ -1155,7 +1155,7 @@ public async Task UnloadFromMyGarages( { sb.Append("fungibleIdAndCounts: ["); sb.Append(string.Join(",", fungibleIdAndCounts.Select(tuple => - $"{{ fungibleId: {{ value: \"{tuple.fungibleId.ToString()}\" }}, " + + $"{{ fungibleId: \"{tuple.fungibleId.ToString()}\", " + $"count: {tuple.count} }}"))); sb.Append("],"); } diff --git a/NineChronicles.Headless/GraphTypes/FungibleIdAndCountInputType.cs b/NineChronicles.Headless/GraphTypes/FungibleIdAndCountInputType.cs index b2dff8b8a..6a94d183f 100644 --- a/NineChronicles.Headless/GraphTypes/FungibleIdAndCountInputType.cs +++ b/NineChronicles.Headless/GraphTypes/FungibleIdAndCountInputType.cs @@ -12,18 +12,19 @@ public FungibleIdAndCountInputType() { Name = "FungibleIdAndCountInput"; - Field>( + Field>( name: "fungibleId", description: "Fungible ID"); - Field( + Field>( name: "count", description: "Count"); } public override object ParseDictionary(IDictionary value) { - var fungibleId = (HashDigest)value["fungibleId"]!; + var hexDigest = (string)value["fungibleId"]!; + var fungibleId = HashDigest.FromString(hexDigest); var count = (int)value["count"]!; return (fungibleId, count); } diff --git a/NineChronicles.Headless/GraphTypes/HashDigestInputType.cs b/NineChronicles.Headless/GraphTypes/HashDigestInputType.cs deleted file mode 100644 index 92ee70a57..000000000 --- a/NineChronicles.Headless/GraphTypes/HashDigestInputType.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; -using System.Security.Cryptography; -using GraphQL.Types; -using Libplanet; - -namespace NineChronicles.Headless.GraphTypes -{ - // FIXME: To StringGraphType - public class HashDigestInputType : InputObjectGraphType> - where T : HashAlgorithm - { - public HashDigestInputType() - { - Name = $"HashDigestInput_{typeof(T).Name}"; - Field( - name: "value", - description: "Hash digest hex string"); - } - - public override object ParseDictionary(IDictionary value) - { - var hashValue = (string)value["value"]!; - return HashDigest.FromString(hashValue); - } - } -} From ca94fe97adf8c59e0433728ee9ee224cb84a8a0e Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Mon, 19 Jun 2023 23:27:22 +0900 Subject: [PATCH 27/89] Fix bug --- NineChronicles.Headless/StandaloneContext.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/NineChronicles.Headless/StandaloneContext.cs b/NineChronicles.Headless/StandaloneContext.cs index 6dfd69afc..f43a037a9 100644 --- a/NineChronicles.Headless/StandaloneContext.cs +++ b/NineChronicles.Headless/StandaloneContext.cs @@ -141,6 +141,7 @@ public bool TryGetFungibleAssetValue( if (TryGetCurrency(ticker, out var currency)) { fungibleAssetValue = FungibleAssetValue.Parse(currency!.Value, value); + return true; } fungibleAssetValue = null; From 5f6da439e79eff1ccb9baf9dfe02125eae729ecc Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Tue, 20 Jun 2023 10:39:06 +0900 Subject: [PATCH 28/89] Fix typo --- NineChronicles.Headless/GraphTypes/ActionQuery.DevEx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NineChronicles.Headless/GraphTypes/ActionQuery.DevEx.cs b/NineChronicles.Headless/GraphTypes/ActionQuery.DevEx.cs index 3a1335c7c..712daa8b4 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQuery.DevEx.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQuery.DevEx.cs @@ -66,7 +66,7 @@ private void RegisterFieldsForDevEx() }), resolve: context => { - if (standaloneContext.BlockChain is not { } chain) + if (StandaloneContext.BlockChain is not { } chain) { throw new InvalidOperationException( "BlockChain not found in the context"); From 56d136ca6106bff8492bd7aadbe3d866f26ae562 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Tue, 20 Jun 2023 10:51:36 +0900 Subject: [PATCH 29/89] Remove empty spaces --- .../GraphTypes/ActionQueryTest.cs | 13 ++++++------- .../GraphTypes/ActionQueryFields/Garages.cs | 6 +++--- .../GraphTypes/FungibleIdAndCountInputType.cs | 2 +- .../SimplifyFungibleAssetValueInputType.cs | 4 ++-- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs index 1a7e4ba69..4db7b6eb2 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs @@ -13,7 +13,6 @@ using Libplanet; using Libplanet.Assets; using Libplanet.Crypto; -using Microsoft.Extensions.Primitives; using Nekoyume; using Nekoyume.Action; using Nekoyume.Action.Garages; @@ -1056,7 +1055,7 @@ public async Task DeliverToOthersGarages( $"value: \"{tuple.GetQuantityString()}\" }}"))); sb.Append("],"); } - + if (fungibleIdAndCounts is not null) { sb.Append("fungibleIdAndCounts: ["); @@ -1065,18 +1064,18 @@ public async Task DeliverToOthersGarages( $"count: {tuple.count} }}"))); sb.Append("],"); } - + if (memo is not null) { sb.Append($"memo: \"{memo}\""); } - + // Remove last ',' if exists. if (sb[^1] == ',') { sb.Remove(sb.Length - 1, 1); } - + sb.Append(") }"); var queryResult = await ExecuteQueryAsync( @@ -1095,7 +1094,7 @@ public async Task DeliverToOthersGarages( action.FungibleIdAndCounts is null); Assert.Equal(expectedAction.Memo, action.Memo); } - + private static IEnumerable GetMemberDataOfDeliverToOthersGarages() { yield return new object[] @@ -1189,7 +1188,7 @@ public async Task UnloadFromMyGarages( action.FungibleIdAndCounts is null); Assert.Equal(expectedAction.Memo, action.Memo); } - + private static IEnumerable GetMemberDataOfUnloadFromMyGarages() { yield return new object[] diff --git a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs index 777a4b291..c97c94720 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs @@ -52,7 +52,7 @@ private void RegisterGarages() { if (StandaloneContext.TryGetFungibleAssetValue(currencyTicker, value, out var fav)) { - fungibleAssetValues.Add((balanceAddr, fav!.Value)); + fungibleAssetValues.Add((balanceAddr, fav!.Value)); } else { @@ -114,7 +114,7 @@ private void RegisterGarages() { if (StandaloneContext.TryGetFungibleAssetValue(currencyTicker, value, out var fav)) { - fungibleAssetValues.Add(fav!.Value); + fungibleAssetValues.Add(fav!.Value); } else { @@ -174,7 +174,7 @@ private void RegisterGarages() { if (StandaloneContext.TryGetFungibleAssetValue(currencyTicker, value, out var fav)) { - fungibleAssetValues.Add((addr, fav!.Value)); + fungibleAssetValues.Add((addr, fav!.Value)); } else { diff --git a/NineChronicles.Headless/GraphTypes/FungibleIdAndCountInputType.cs b/NineChronicles.Headless/GraphTypes/FungibleIdAndCountInputType.cs index 6a94d183f..61164c9e2 100644 --- a/NineChronicles.Headless/GraphTypes/FungibleIdAndCountInputType.cs +++ b/NineChronicles.Headless/GraphTypes/FungibleIdAndCountInputType.cs @@ -24,7 +24,7 @@ public FungibleIdAndCountInputType() public override object ParseDictionary(IDictionary value) { var hexDigest = (string)value["fungibleId"]!; - var fungibleId = HashDigest.FromString(hexDigest); + var fungibleId = HashDigest.FromString(hexDigest); var count = (int)value["count"]!; return (fungibleId, count); } diff --git a/NineChronicles.Headless/GraphTypes/SimplifyFungibleAssetValueInputType.cs b/NineChronicles.Headless/GraphTypes/SimplifyFungibleAssetValueInputType.cs index 51310f820..d30df9768 100644 --- a/NineChronicles.Headless/GraphTypes/SimplifyFungibleAssetValueInputType.cs +++ b/NineChronicles.Headless/GraphTypes/SimplifyFungibleAssetValueInputType.cs @@ -35,9 +35,9 @@ public override object ParseDictionary(IDictionary value) { if (value.ContainsKey("currencyTicker")) { - throw new ExecutionError("currencyEnum and currencyTicker cannot be specified at the same time."); + throw new ExecutionError("currencyEnum and currencyTicker cannot be specified at the same time."); } - + var currencyTicker = ((CurrencyEnum)currencyEnum!).ToString(); return (currencyTicker, value: value2); } From ba53d87964fc0b10a6441d1b2a1506f1cb257d28 Mon Sep 17 00:00:00 2001 From: area363 Date: Thu, 22 Jun 2023 11:17:12 +0900 Subject: [PATCH 30/89] add submodule-updater to bump Data Provider --- .github/workflows/update-submodule.yaml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/update-submodule.yaml diff --git a/.github/workflows/update-submodule.yaml b/.github/workflows/update-submodule.yaml new file mode 100644 index 000000000..4f2e7d60f --- /dev/null +++ b/.github/workflows/update-submodule.yaml @@ -0,0 +1,21 @@ +name: update-submodule + +on: + push: + branches: + - rc-v* + - release/* + +jobs: + update-submodule: + if: github.ref_type == 'branch' + runs-on: ubuntu-latest + steps: + - name: Update other repos referring NineChronicles.Headless as submodules + uses: planetarium/submodule-updater@main + with: + token: ${{ secrets.SUBMODULE_UPDATER_GH_TOKEN }} + committer: > + Submodule Updater + targets: | + ${{ github.repository_owner }}/NineChronicles.DataProvider:${{ github.ref_name }}? From 2a1cce28e2c38129d53d9bf44e291d876fee2b86 Mon Sep 17 00:00:00 2001 From: moreal Date: Thu, 22 Jun 2023 14:20:01 +0900 Subject: [PATCH 31/89] ci(gh-actions): remove deploy_gh_pages workflow --- .github/workflows/deploy_gh_pages.yml | 61 --------------------------- 1 file changed, 61 deletions(-) delete mode 100644 .github/workflows/deploy_gh_pages.yml diff --git a/.github/workflows/deploy_gh_pages.yml b/.github/workflows/deploy_gh_pages.yml deleted file mode 100644 index 716c6201f..000000000 --- a/.github/workflows/deploy_gh_pages.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Deploy gh pages -on: - push: - branches: - - development -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: checkout - uses: actions/checkout@v3 - with: - submodules: recursive - - name: Set up Node.js - uses: actions/setup-node@v2 - with: - node-version: '14' - - uses: actions/setup-dotnet@v3 - name: Set up .NET Core SDK - with: - dotnet-version: 6.0.x - - name: Build GraphQL Schema - run: | - dotnet restore - dotnet build - dotnet run --project NineChronicles.Headless.Executable -- \ - --graphql-server \ - --graphql-port 30000 \ - --graphql-host localhost \ - --store-path /tmp/store \ - --no-miner \ - --no-cors \ - --skip-preload \ - -H localhost \ - -V "0/CbfC996ad185c61a031f40CeeE80a055e6D83005/MEUCIQCtoZmiFgg5NXW7+5jYMae80lTlj7xO4tQfX9CnvomAtwIgWViM8s.4mYQ89wlGkohmynZ43olDzZLBk.bHShKCVrc=" \ - -G "https://download.nine-chronicles.com/genesis-block-9c-main" & - sleep 180s - wget http://localhost:30000/schema.graphql -O schema.graphql - - 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: Copy GraphQL Schema to deploy - run: cp schema.graphql doc - - name: Deploy - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./doc From 71dea7ab9fcfff3ef4b4a683c8e9b6ed5a7093ed Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Thu, 22 Jun 2023 14:22:33 +0900 Subject: [PATCH 32/89] Bump lib9c:2023q2-iap --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 54f7f9c53..8bd50ada1 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 54f7f9c5312e15ffde6b571cbda5aef4cbc09530 +Subproject commit 8bd50ada11dd0d5d19a20853bc355b8382cad5cf From 0d49543b65d8477a20415be3651348c457a9837d Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Fri, 23 Jun 2023 14:36:07 +0900 Subject: [PATCH 33/89] Bump lib9c to f610a3de --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 8d1777521..f610a3dec 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 8d1777521ade6c900b8853283312f3cb06871af9 +Subproject commit f610a3dec7f0560986649784bd4fa0fe13a041b1 From ea6421850420f0b76b2686052dc32df5d609ba9d Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Fri, 23 Jun 2023 15:26:48 +0900 Subject: [PATCH 34/89] Accommodate libplanet API changes --- .../ActionEvaluationSerializerTest.cs | 2 + .../AccountStateDelta.cs | 7 ++- .../ActionContext.cs | 29 ++++++++++-- .../ActionContextMarshaller.cs | 2 + .../ForkableActionEvaluatorTest.cs | 2 + .../Commands/ReplayCommand.Privates.cs | 46 +++++++++++-------- .../Commands/ReplayCommand.cs | 6 +-- 7 files changed, 65 insertions(+), 29 deletions(-) diff --git a/Libplanet.Extensions.ActionEvaluatorCommonComponents.Tests/ActionEvaluationSerializerTest.cs b/Libplanet.Extensions.ActionEvaluatorCommonComponents.Tests/ActionEvaluationSerializerTest.cs index 7729b8f58..1aa6bc071 100644 --- a/Libplanet.Extensions.ActionEvaluatorCommonComponents.Tests/ActionEvaluationSerializerTest.cs +++ b/Libplanet.Extensions.ActionEvaluatorCommonComponents.Tests/ActionEvaluationSerializerTest.cs @@ -27,6 +27,7 @@ public void Serialization() null, addresses[1], 0, + 0, false, previousStates, new Random(123), @@ -41,6 +42,7 @@ public void Serialization() Assert.Equal(Null.Value, deserialized.Action); Assert.Equal(123, deserialized.InputContext.Random.Seed); Assert.Equal(0, deserialized.InputContext.BlockIndex); + Assert.Equal(0, deserialized.InputContext.BlockProtocolVersion); Assert.Equal(new[] { "one", "two" }, deserialized.Logs); Assert.Equal(addresses[0], deserialized.InputContext.Signer); Assert.Equal(addresses[1], deserialized.InputContext.Miner); diff --git a/Libplanet.Extensions.ActionEvaluatorCommonComponents/AccountStateDelta.cs b/Libplanet.Extensions.ActionEvaluatorCommonComponents/AccountStateDelta.cs index 3eca4cc35..8c2d3009d 100644 --- a/Libplanet.Extensions.ActionEvaluatorCommonComponents/AccountStateDelta.cs +++ b/Libplanet.Extensions.ActionEvaluatorCommonComponents/AccountStateDelta.cs @@ -149,7 +149,8 @@ public FungibleAssetValue GetTotalSupply(Currency currency) return TotalSupplyGetter(currency); } - public IAccountStateDelta MintAsset(Address recipient, FungibleAssetValue value) + public IAccountStateDelta MintAsset( + IActionContext context, Address recipient, FungibleAssetValue value) { // FIXME: 트랜잭션 서명자를 알아내 currency.AllowsToMint() 확인해서 CurrencyPermissionException // 던지는 처리를 해야하는데 여기서 트랜잭션 서명자를 무슨 수로 가져올지 잘 모르겠음. @@ -209,6 +210,7 @@ public IAccountStateDelta MintAsset(Address recipient, FungibleAssetValue value) } public IAccountStateDelta TransferAsset( + IActionContext context, Address sender, Address recipient, FungibleAssetValue value, @@ -244,7 +246,8 @@ public IAccountStateDelta TransferAsset( }; } - public IAccountStateDelta BurnAsset(Address owner, FungibleAssetValue value) + public IAccountStateDelta BurnAsset( + IActionContext context, Address owner, FungibleAssetValue value) { // FIXME: 트랜잭션 서명자를 알아내 currency.AllowsToMint() 확인해서 CurrencyPermissionException // 던지는 처리를 해야하는데 여기서 트랜잭션 서명자를 무슨 수로 가져올지 잘 모르겠음. diff --git a/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionContext.cs b/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionContext.cs index 177915120..ed15dbf24 100644 --- a/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionContext.cs +++ b/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionContext.cs @@ -9,8 +9,17 @@ namespace Libplanet.Extensions.ActionEvaluatorCommonComponents; public class ActionContext : IActionContext { - public ActionContext(BlockHash? genesisHash, Address signer, TxId? txId, Address miner, long blockIndex, - bool rehearsal, AccountStateDelta previousStates, IRandom random, HashDigest? previousStateRootHash, + public ActionContext( + BlockHash? genesisHash, + Address signer, + TxId? txId, + Address miner, + long blockIndex, + int blockProtocolVersion, + bool rehearsal, + AccountStateDelta previousStates, + IRandom random, + HashDigest? previousStateRootHash, bool blockAction) { GenesisHash = genesisHash; @@ -18,6 +27,7 @@ public ActionContext(BlockHash? genesisHash, Address signer, TxId? txId, Address TxId = txId; Miner = miner; BlockIndex = blockIndex; + BlockProtocolVersion = blockProtocolVersion; Rehearsal = rehearsal; PreviousStates = previousStates; Random = random; @@ -30,6 +40,7 @@ public ActionContext(BlockHash? genesisHash, Address signer, TxId? txId, Address public TxId? TxId { get; } public Address Miner { get; init; } public long BlockIndex { get; init; } + public int BlockProtocolVersion { get; init; } public bool Rehearsal { get; init; } public AccountStateDelta PreviousStates { get; init; } IAccountStateDelta IActionContext.PreviousStates => PreviousStates; @@ -49,8 +60,18 @@ public void UseGas(long gas) public IActionContext GetUnconsumedContext() { - return new ActionContext(GenesisHash, Signer, TxId, Miner, BlockIndex, Rehearsal, PreviousStates, - new Random(Random.Seed), PreviousStateRootHash, BlockAction); + return new ActionContext( + GenesisHash, + Signer, + TxId, + Miner, + BlockIndex, + BlockProtocolVersion, + Rehearsal, + PreviousStates, + new Random(Random.Seed), + PreviousStateRootHash, + BlockAction); } public long GasUsed() => 0; diff --git a/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionContextMarshaller.cs b/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionContextMarshaller.cs index 39b14ba3c..6438da9ea 100644 --- a/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionContextMarshaller.cs +++ b/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionContextMarshaller.cs @@ -24,6 +24,7 @@ public static Dictionary Marshal(this IActionContext actionContext) .Add("miner", actionContext.Miner.ToHex()) .Add("rehearsal", actionContext.Rehearsal) .Add("block_index", actionContext.BlockIndex) + .Add("block_protocol_version", actionContext.BlockProtocolVersion) .Add("random_seed", actionContext.Random.Seed) .Add("signer", actionContext.Signer.ToHex()) .Add("previous_states", AccountStateDeltaMarshaller.Marshal(actionContext.PreviousStates)); @@ -44,6 +45,7 @@ genesisHashValue is Binary genesisHashBinaryValue ? new BlockHash(genesisHashBinaryValue.ByteArray) : null, blockIndex: (Integer)dictionary["block_index"], + blockProtocolVersion: (Integer)dictionary["block_protocol_version"], signer: new Address(((Text)dictionary["signer"]).Value), txId: dictionary.TryGetValue((Text)"tx_id", out IValue txIdValue) && txIdValue is Binary txIdBinaryValue diff --git a/Libplanet.Extensions.ForkableActionEvaluator.Tests/ForkableActionEvaluatorTest.cs b/Libplanet.Extensions.ForkableActionEvaluator.Tests/ForkableActionEvaluatorTest.cs index 80cce28ac..8cd5f6d62 100644 --- a/Libplanet.Extensions.ForkableActionEvaluator.Tests/ForkableActionEvaluatorTest.cs +++ b/Libplanet.Extensions.ForkableActionEvaluator.Tests/ForkableActionEvaluatorTest.cs @@ -75,6 +75,7 @@ public IReadOnlyList Evaluate(IPreEvaluationBlock block) null, default, 0, + 0, false, new AccountStateDelta(), new Random(0), @@ -102,6 +103,7 @@ public IReadOnlyList Evaluate(IPreEvaluationBlock block) null, default, 0, + 0, false, new AccountStateDelta(), new Random(0), diff --git a/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs b/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs index 77062903f..4f95ce648 100644 --- a/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs +++ b/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs @@ -66,14 +66,11 @@ public IImmutableDictionary> protected ValidatorSet? UpdatedValidatorSet { get; set; } = null; - protected Address Signer { get; set; } - public AccountStateDeltaImpl( AccountStateGetter accountStateGetter, AccountBalanceGetter accountBalanceGetter, TotalSupplyGetter totalSupplyGetter, - ValidatorSetGetter validatorSetGetter, - Address signer) + ValidatorSetGetter validatorSetGetter) { StateGetter = accountStateGetter; BalanceGetter = accountBalanceGetter; @@ -82,7 +79,6 @@ public AccountStateDeltaImpl( UpdatedStates = ImmutableDictionary.Empty; UpdatedFungibles = ImmutableDictionary<(Address, Currency), BigInteger>.Empty; UpdatedTotalSupply = ImmutableDictionary.Empty; - Signer = signer; } public IValue? GetState(Address address) => @@ -149,7 +145,8 @@ public FungibleAssetValue GetTotalSupply(Currency currency) public IAccountStateDelta SetState(Address address, IValue state) => UpdateStates(UpdatedStates.SetItem(address, state)); - public IAccountStateDelta MintAsset(Address recipient, FungibleAssetValue value) + public IAccountStateDelta MintAsset( + IActionContext context, Address recipient, FungibleAssetValue value) { if (value.Sign <= 0) { @@ -160,11 +157,11 @@ public IAccountStateDelta MintAsset(Address recipient, FungibleAssetValue value) } Currency currency = value.Currency; - if (!currency.AllowsToMint(Signer)) + if (!currency.AllowsToMint(context.Signer)) { throw new CurrencyPermissionException( - $"The account {Signer} has no permission to mint the currency {currency}.", - Signer, + $"The account {context.Signer} has no permission to mint currency {currency}.", + context.Signer, currency ); } @@ -193,7 +190,11 @@ public IAccountStateDelta MintAsset(Address recipient, FungibleAssetValue value) ); } - public IAccountStateDelta TransferAsset(Address sender, Address recipient, FungibleAssetValue value, + public IAccountStateDelta TransferAsset( + IActionContext context, + Address sender, + Address recipient, + FungibleAssetValue value, bool allowNegativeBalance = false) { if (value.Sign <= 0) @@ -229,7 +230,8 @@ public IAccountStateDelta TransferAsset(Address sender, Address recipient, Fungi ); } - public IAccountStateDelta BurnAsset(Address owner, FungibleAssetValue value) + public IAccountStateDelta BurnAsset( + IActionContext context, Address owner, FungibleAssetValue value) { string msg; @@ -242,11 +244,11 @@ public IAccountStateDelta BurnAsset(Address owner, FungibleAssetValue value) } Currency currency = value.Currency; - if (!currency.AllowsToMint(Signer)) + if (!currency.AllowsToMint(context.Signer)) { - msg = $"The account {Signer} has no permission to burn assets of " + + msg = $"The account {context.Signer} has no permission to burn assets of " + $"the currency {currency}."; - throw new CurrencyPermissionException(msg, Signer, currency); + throw new CurrencyPermissionException(msg, context.Signer, currency); } FungibleAssetValue balance = GetBalance(owner, currency); @@ -332,8 +334,7 @@ IImmutableDictionary updatedStates StateGetter, BalanceGetter, TotalSupplyGetter, - ValidatorSetGetter, - Signer) + ValidatorSetGetter) { UpdatedStates = updatedStates, UpdatedFungibles = UpdatedFungibles, @@ -349,8 +350,7 @@ IImmutableDictionary updatedTotalSupply StateGetter, BalanceGetter, TotalSupplyGetter, - ValidatorSetGetter, - Signer) + ValidatorSetGetter) { UpdatedStates = UpdatedStates, UpdatedFungibles = updatedFungibleAssets, @@ -383,8 +383,7 @@ ValidatorSet updatedValidatorSet StateGetter, BalanceGetter, TotalSupplyGetter, - ValidatorSetGetter, - Signer) + ValidatorSetGetter) { UpdatedStates = UpdatedStates, UpdatedFungibles = UpdatedFungibles, @@ -420,6 +419,7 @@ public ActionContext( TxId? txid, Address miner, long blockIndex, + int blockProtocolVersion, IAccountStateDelta previousStates, int randomSeed, bool rehearsal = false, @@ -431,6 +431,7 @@ public ActionContext( TxId = txid; Miner = miner; BlockIndex = blockIndex; + BlockProtocolVersion = blockProtocolVersion; Rehearsal = rehearsal; PreviousStates = previousStates; Random = new Random(randomSeed); @@ -449,6 +450,8 @@ public ActionContext( public long BlockIndex { get; } + public int BlockProtocolVersion { get; } + public bool Rehearsal { get; } public IAccountStateDelta PreviousStates { get; } @@ -482,6 +485,7 @@ public IActionContext GetUnconsumedContext() => TxId, Miner, BlockIndex, + BlockProtocolVersion, PreviousStates, _randomSeed, Rehearsal, @@ -567,6 +571,7 @@ private static IEnumerable EvaluateActions( BlockHash? genesisHash, ImmutableArray preEvaluationHash, long blockIndex, + int blockProtocolVersion, TxId? txid, IAccountStateDelta previousStates, Address miner, @@ -586,6 +591,7 @@ ActionContext CreateActionContext(IAccountStateDelta prevStates, int randomSeed) txid: txid, miner: miner, blockIndex: blockIndex, + blockProtocolVersion: blockProtocolVersion, previousStates: prevStates, randomSeed: randomSeed, rehearsal: rehearsal, diff --git a/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs b/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs index 97f1140b8..30af245f1 100644 --- a/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs @@ -103,13 +103,13 @@ public int Tx( currency, previousBlock.Hash), () => blockChain.GetValidatorSet( - previousBlock.Hash), - tx.Signer); + previousBlock.Hash)); var actions = tx.Actions.Select(a => ToAction(a)); var actionEvaluations = EvaluateActions( genesisHash: blockChain.Genesis.Hash, preEvaluationHash: targetBlock.PreEvaluationHash.ByteArray, - blockIndex: blockIndex, + blockIndex: targetBlock.Index, + blockProtocolVersion: targetBlock.ProtocolVersion, txid: tx.Id, previousStates: previousStates, miner: targetBlock.Miner, From 4cf3338f4ed97e5727a8980225d3a3dffc04888c Mon Sep 17 00:00:00 2001 From: hyeon Date: Tue, 27 Jun 2023 14:26:15 +0900 Subject: [PATCH 35/89] Add MEAD minting action to genesis block --- .../Commands/GenesisCommand.cs | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/NineChronicles.Headless.Executable/Commands/GenesisCommand.cs b/NineChronicles.Headless.Executable/Commands/GenesisCommand.cs index 7eac9bc1d..cf39cfad9 100644 --- a/NineChronicles.Headless.Executable/Commands/GenesisCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/GenesisCommand.cs @@ -7,8 +7,9 @@ using Bencodex; using Bencodex.Types; using Cocona; +using Lib9c; using Libplanet; -using Libplanet.Action; +using Libplanet.Assets; using Libplanet.Blocks; using Libplanet.Crypto; using Libplanet.Extensions.Cocona; @@ -16,7 +17,6 @@ using Nekoyume.Action; using Nekoyume.Model.State; using NineChronicles.Headless.Executable.IO; -using Serilog; using Lib9cUtils = Lib9c.DevExtensions.Utils; namespace NineChronicles.Headless.Executable.Commands @@ -94,12 +94,15 @@ out List initialDepositList } } - private void ProcessAdmin(AdminConfig? config, PrivateKey initialMinter, out AdminState adminState) + private void ProcessAdmin(AdminConfig? config, PrivateKey initialMinter, + out AdminState adminState, out List meadActions) { // FIXME: If the `adminState` is not required inside `MineGenesisBlock`, // this logic will be much lighter. _console.Out.WriteLine("\nProcessing admin for genesis..."); adminState = new AdminState(new Address(), 0); + meadActions = new List(); + if (config is null) { _console.Out.WriteLine("AdminConfig not provided. Skip admin setting..."); @@ -112,10 +115,26 @@ private void ProcessAdmin(AdminConfig? config, PrivateKey initialMinter, out Adm { _console.Out.WriteLine("Admin address not provided. Give admin privilege to initialMinter"); adminState = new AdminState(initialMinter.ToAddress(), config.Value.ValidUntil); + meadActions.Add(new PrepareRewardAssets + { + RewardPoolAddress = initialMinter.ToAddress(), + Assets = new List + { + 10000 * Currencies.Mead, + }, + }); } else { adminState = new AdminState(new Address(config.Value.Address), config.Value.ValidUntil); + meadActions.Add(new PrepareRewardAssets + { + RewardPoolAddress = new Address(config.Value.Address), + Assets = new List + { + 10000 * Currencies.Mead, + }, + }); } } else @@ -197,12 +216,11 @@ public void Mine( ProcessCurrency(genesisConfig.Currency, out var initialMinter, out var initialDepositList); - ProcessAdmin(genesisConfig.Admin, initialMinter, out var adminState); + ProcessAdmin(genesisConfig.Admin, initialMinter, out var adminState, out var meadActions); ProcessValidator(genesisConfig.InitialValidatorSet, initialMinter, out var initialValidatorSet); - ProcessExtra(genesisConfig.Extra, - out var pendingActivationStates); + ProcessExtra(genesisConfig.Extra, out var pendingActivationStates); // Mine genesis block _console.Out.WriteLine("\nMining genesis block...\n"); @@ -214,7 +232,8 @@ public void Mine( privateKey: initialMinter, initialValidators: initialValidatorSet.ToDictionary( item => new PublicKey(ByteUtil.ParseHex(item.PublicKey)), - item => new BigInteger(item.Power)) + item => new BigInteger(item.Power)), + actionBases: meadActions ); Lib9cUtils.ExportBlock(block, "genesis-block"); From 60fa56215464bb3902739229eaa66731dd799a0f Mon Sep 17 00:00:00 2001 From: hyeon Date: Wed, 28 Jun 2023 02:27:12 +0900 Subject: [PATCH 36/89] Add garage state GQL --- .../GraphTypes/StateQuery.cs | 28 +++++++++++++++++++ .../GraphTypes/States/GarageStateType.cs | 22 +++++++++++++++ .../Models/Garage/FungibleItemGarageType.cs | 13 +++++++++ 3 files changed, 63 insertions(+) create mode 100644 NineChronicles.Headless/GraphTypes/States/GarageStateType.cs create mode 100644 NineChronicles.Headless/GraphTypes/States/Models/Garage/FungibleItemGarageType.cs diff --git a/NineChronicles.Headless/GraphTypes/StateQuery.cs b/NineChronicles.Headless/GraphTypes/StateQuery.cs index 787c4335b..89a77cefd 100644 --- a/NineChronicles.Headless/GraphTypes/StateQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StateQuery.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Cryptography; using Bencodex.Types; using GraphQL; using GraphQL.Types; +using Lib9c; using Lib9c.Model.Order; using Libplanet; using Libplanet.Assets; @@ -539,6 +541,32 @@ public StateQuery() return (address, approved, mead); } ); + + Field( + "garage", + arguments: new QueryArguments( + new QueryArgument> + { + Name = "address", + Description = "Address to get GARAGE token balance and fungible items" + }, + new QueryArgument> + { + Name = "fungibleItemIds", + Description = "List of fungible item IDs to get stock in garage" + } + ), + resolve: context => + { + var address = context.GetArgument
("address"); + var balance = context.Source.GetBalance(address, Currencies.Garage); + var fungibleItemIdList = context.GetArgument>("fungibleItemIds"); + IEnumerable
fungibleItemAddressList = fungibleItemIdList.Select(fungibleItemId => + Addresses.GetGarageAddress(address, HashDigest.FromString(fungibleItemId))); + var fungibleItemList = context.Source.GetStates(fungibleItemAddressList.ToArray()); + return (balance, fungibleItemList); + } + ); } } } diff --git a/NineChronicles.Headless/GraphTypes/States/GarageStateType.cs b/NineChronicles.Headless/GraphTypes/States/GarageStateType.cs new file mode 100644 index 000000000..d52014708 --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/States/GarageStateType.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Security.Cryptography; +using Bencodex.Types; +using GraphQL.Types; +using Lib9c; +using Libplanet; +using Libplanet.Assets; +using Libplanet.State; +using Nekoyume.Model.Garages; +using NineChronicles.Headless.GraphTypes.States.Models.Garage; + +namespace NineChronicles.Headless.GraphTypes.States; + +public class GarageStateType : ObjectGraphType<(FungibleAssetValue balance, IReadOnlyList fungibleItemList)> +{ + public GarageStateType() + { + Field(name: "balance", resolve: context => context.Source.balance); + Field>(name: "fungibleItemList", + resolve: context => context.Source.fungibleItemList); + } +} diff --git a/NineChronicles.Headless/GraphTypes/States/Models/Garage/FungibleItemGarageType.cs b/NineChronicles.Headless/GraphTypes/States/Models/Garage/FungibleItemGarageType.cs new file mode 100644 index 000000000..d5f85ca63 --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/States/Models/Garage/FungibleItemGarageType.cs @@ -0,0 +1,13 @@ +using GraphQL.Types; +using Nekoyume.Model.Garages; + +namespace NineChronicles.Headless.GraphTypes.States.Models.Garage; + +public class FungibleItemGarageType: ObjectGraphType +{ + public FungibleItemGarageType() + { + Field(name: "fungibleItemId", resolve: context => context.Source.Item.FungibleId); + Field(name: "count", resolve: context => context.Source.Count); + } +} From bd8f91590fcf83c10c859e2c6b68fb0cc67ef4f1 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Wed, 28 Jun 2023 13:39:52 +0900 Subject: [PATCH 37/89] Bump lib9c development --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 79d36a1f6..c4f36dedd 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 79d36a1f6c62dac3398e6c237fda7ef47aa13644 +Subproject commit c4f36dedd7193a5694c306c7f8ce58bd34d8e532 From bde43d4b12b5b5fc8b5111366409e9eb4467d791 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Wed, 28 Jun 2023 18:15:31 +0900 Subject: [PATCH 38/89] Bump lib9c:2023q2-iap --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 8bd50ada1..af36b8ef1 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 8bd50ada11dd0d5d19a20853bc355b8382cad5cf +Subproject commit af36b8ef123ebaeb8ae87b8bd9ce09b64fe9cc73 From e79aedee0ef49134338e56156549c63dce4aeb48 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Wed, 28 Jun 2023 18:35:17 +0900 Subject: [PATCH 39/89] Apply changes of `UnloadFromMyGarages` action to query --- .../GraphTypes/ActionQueryTest.cs | 16 ++++++---------- .../GraphTypes/ActionQueryFields/Garages.cs | 14 +++++++------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs index 75c89b61d..c920f7394 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs @@ -1204,17 +1204,18 @@ private static IEnumerable GetMemberDataOfDeliverToOthersGarages() [Theory] [MemberData(nameof(GetMemberDataOfUnloadFromMyGarages))] public async Task UnloadFromMyGarages( + Address recipientAvatarAddr, IEnumerable<(Address balanceAddr, FungibleAssetValue value)>? fungibleAssetValues, - Address? inventoryAddr, IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts, string? memo) { var expectedAction = new UnloadFromMyGarages( + recipientAvatarAddr, fungibleAssetValues, - inventoryAddr, fungibleIdAndCounts, memo); var sb = new StringBuilder("{ unloadFromMyGarages("); + sb.Append($"recipientAvatarAddr: \"{recipientAvatarAddr.ToHex()}\","); if (fungibleAssetValues is not null) { sb.Append("fungibleAssetValues: ["); @@ -1225,11 +1226,6 @@ public async Task UnloadFromMyGarages( sb.Append("],"); } - if (inventoryAddr is not null) - { - sb.Append($"inventoryAddr: \"{inventoryAddr.Value.ToHex()}\","); - } - if (fungibleIdAndCounts is not null) { sb.Append("fungibleIdAndCounts: ["); @@ -1261,9 +1257,9 @@ public async Task UnloadFromMyGarages( Assert.IsType(plainValue); var actionBase = DeserializeNCAction(plainValue); var action = Assert.IsType(actionBase); + Assert.Equal(expectedAction.RecipientAvatarAddr, action.RecipientAvatarAddr); Assert.True(expectedAction.FungibleAssetValues?.SequenceEqual(action.FungibleAssetValues) ?? action.FungibleAssetValues is null); - Assert.Equal(expectedAction.InventoryAddr, action.InventoryAddr); Assert.True(expectedAction.FungibleIdAndCounts?.SequenceEqual(action.FungibleIdAndCounts) ?? action.FungibleIdAndCounts is null); Assert.Equal(expectedAction.Memo, action.Memo); @@ -1273,13 +1269,14 @@ private static IEnumerable GetMemberDataOfUnloadFromMyGarages() { yield return new object[] { + new PrivateKey().ToAddress(), null, null, null, - "memo", }; yield return new object[] { + new PrivateKey().ToAddress(), new[] { ( @@ -1291,7 +1288,6 @@ private static IEnumerable GetMemberDataOfUnloadFromMyGarages() fungibleAssetValue: new FungibleAssetValue(Currencies.Garage, 1, 0) ), }, - new PrivateKey().ToAddress(), new[] { (fungibleId: new HashDigest(), count: 1), diff --git a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs index c97c94720..06f847ef9 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs @@ -140,16 +140,16 @@ private void RegisterGarages() Field>( "unloadFromMyGarages", arguments: new QueryArguments( + new QueryArgument> + { + Name = "recipientAvatarAddr", + Description = "Recipient avatar address", + }, new QueryArgument>> { Name = "fungibleAssetValues", Description = "Array of balance address and currency ticker and quantity to send.", }, - new QueryArgument - { - Name = "inventoryAddr", - Description = "Inventory address to receive items.", - }, new QueryArgument>> { Name = "fungibleIdAndCounts", @@ -163,6 +163,7 @@ private void RegisterGarages() ), resolve: context => { + var recipientAvatarAddr = context.GetArgument
("recipientAvatarAddr"); var balanceInputList = context.GetArgument?>("fungibleAssetValues"); @@ -183,15 +184,14 @@ private void RegisterGarages() } } - var inventoryAddr = context.GetArgument("inventoryAddr"); var fungibleIdAndCounts = context.GetArgument fungibleId, int count)>?>("fungibleIdAndCounts"); var memo = context.GetArgument("memo"); ActionBase action = new UnloadFromMyGarages( + recipientAvatarAddr, fungibleAssetValues, - inventoryAddr, fungibleIdAndCounts, memo); return Encode(context, action); From 698d66bdb9c79bcd10220282632f92f02f775298 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Wed, 28 Jun 2023 18:47:50 +0900 Subject: [PATCH 40/89] Reformat --- .../GraphTypes/ActionQueryTest.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs index c920f7394..2ff7797f3 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs @@ -142,6 +142,7 @@ private class StakeFixture : IEnumerable IEnumerator IEnumerable.GetEnumerator() => _data.GetEnumerator(); } + [Theory] [InlineData("false", false)] [InlineData("true", true)] @@ -150,11 +151,13 @@ public async Task Grinding(string chargeApValue, bool chargeAp) { var avatarAddress = new PrivateKey().ToAddress(); var equipmentId = Guid.NewGuid(); - string queryArgs = $"avatarAddress: \"{avatarAddress.ToString()}\", equipmentIds: [{string.Format($"\"{equipmentId}\"")}]"; + string queryArgs = + $"avatarAddress: \"{avatarAddress.ToString()}\", equipmentIds: [{string.Format($"\"{equipmentId}\"")}]"; if (!string.IsNullOrEmpty(chargeApValue)) { queryArgs += $", chargeAp: {chargeApValue}"; } + string query = $@" {{ grinding({queryArgs}) @@ -241,6 +244,7 @@ public async Task TransferAsset(string currencyType, bool memo) { args += ", memo: \"memo\""; } + var query = $"{{ transferAsset({args}) }}"; var queryResult = await ExecuteQueryAsync(query, standaloneContext: _standaloneContext); var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; @@ -456,6 +460,7 @@ public async Task PrepareRewardAssets(bool mintersExist, int expectedCount) { assets += $", {{quantity: 100, decimalPlaces: 2, ticker: \"NCG\", minters: [\"{rewardPoolAddress}\"]}}"; } + var query = $"{{ prepareRewardAssets(rewardPoolAddress: \"{rewardPoolAddress}\", assets: [{assets}]) }}"; var queryResult = await ExecuteQueryAsync(query, standaloneContext: _standaloneContext); var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; @@ -495,17 +500,21 @@ public async Task TransferAssets(bool exc) var count = 0; while (count < Nekoyume.Action.TransferAssets.RecipientsCapacity) { - recipients += $", {{ recipient: \"{sender}\", amount: {{ quantity: 100, decimalPlaces: 18, ticker: \"CRYSTAL\" }} }}, {{ recipient: \"{sender}\", amount: {{ quantity: 100, decimalPlaces: 0, ticker: \"RUNE_FENRIR1\" }} }}"; + recipients += + $", {{ recipient: \"{sender}\", amount: {{ quantity: 100, decimalPlaces: 18, ticker: \"CRYSTAL\" }} }}, {{ recipient: \"{sender}\", amount: {{ quantity: 100, decimalPlaces: 0, ticker: \"RUNE_FENRIR1\" }} }}"; count++; } } + var query = $"{{ transferAssets(sender: \"{sender}\", recipients: [{recipients}]) }}"; var queryResult = await ExecuteQueryAsync(query, standaloneContext: _standaloneContext); if (exc) { var error = Assert.Single(queryResult.Errors!); - Assert.Contains($"recipients must be less than or equal {Nekoyume.Action.TransferAssets.RecipientsCapacity}.", error.Message); + Assert.Contains( + $"recipients must be less than or equal {Nekoyume.Action.TransferAssets.RecipientsCapacity}.", + error.Message); } else { @@ -1013,8 +1022,8 @@ public async Task CreatePledge(int? mead, int expected) Assert.Equal(expected, action.Mead); } - [Theory] - [MemberData(nameof(GetMemberDataOfLoadIntoMyGarages))] + [Theory] + [MemberData(nameof(GetMemberDataOfLoadIntoMyGarages))] public async Task LoadIntoMyGarages( IEnumerable<(Address balanceAddr, FungibleAssetValue value)>? fungibleAssetValues, Address? inventoryAddr, From 41aed8dc0ebbe62a74bbc7a50d61bba57c1c0b92 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Wed, 28 Jun 2023 18:58:37 +0900 Subject: [PATCH 41/89] Linting --- NineChronicles.Headless.Executable/Commands/GenesisCommand.cs | 2 +- .../GraphTypes/States/Models/Garage/FungibleItemGarageType.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NineChronicles.Headless.Executable/Commands/GenesisCommand.cs b/NineChronicles.Headless.Executable/Commands/GenesisCommand.cs index cf39cfad9..a0bf5d42b 100644 --- a/NineChronicles.Headless.Executable/Commands/GenesisCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/GenesisCommand.cs @@ -102,7 +102,7 @@ private void ProcessAdmin(AdminConfig? config, PrivateKey initialMinter, _console.Out.WriteLine("\nProcessing admin for genesis..."); adminState = new AdminState(new Address(), 0); meadActions = new List(); - + if (config is null) { _console.Out.WriteLine("AdminConfig not provided. Skip admin setting..."); diff --git a/NineChronicles.Headless/GraphTypes/States/Models/Garage/FungibleItemGarageType.cs b/NineChronicles.Headless/GraphTypes/States/Models/Garage/FungibleItemGarageType.cs index d5f85ca63..a1f226ecd 100644 --- a/NineChronicles.Headless/GraphTypes/States/Models/Garage/FungibleItemGarageType.cs +++ b/NineChronicles.Headless/GraphTypes/States/Models/Garage/FungibleItemGarageType.cs @@ -3,7 +3,7 @@ namespace NineChronicles.Headless.GraphTypes.States.Models.Garage; -public class FungibleItemGarageType: ObjectGraphType +public class FungibleItemGarageType : ObjectGraphType { public FungibleItemGarageType() { From cdf2855c77b8355e145e64d6fcf2f5a5f285f2c9 Mon Sep 17 00:00:00 2001 From: moreal Date: Fri, 30 Jun 2023 11:42:42 +0900 Subject: [PATCH 42/89] Deprecate activation-related queries --- NineChronicles.Headless/GraphTypes/ActionQuery.cs | 1 + .../GraphTypes/ActivationStatusMutation.cs | 3 +++ NineChronicles.Headless/GraphTypes/ActivationStatusQuery.cs | 4 ++++ NineChronicles.Headless/GraphTypes/StandaloneMutation.cs | 3 ++- NineChronicles.Headless/GraphTypes/StandaloneQuery.cs | 3 +++ 5 files changed, 13 insertions(+), 1 deletion(-) diff --git a/NineChronicles.Headless/GraphTypes/ActionQuery.cs b/NineChronicles.Headless/GraphTypes/ActionQuery.cs index e4e136591..ee0906862 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQuery.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQuery.cs @@ -404,6 +404,7 @@ public ActionQuery(StandaloneContext standaloneContext) ); Field>( "activateAccount", + deprecationReason: "Since NCIP-15, it doesn't care account activation.", arguments: new QueryArguments( new QueryArgument> { diff --git a/NineChronicles.Headless/GraphTypes/ActivationStatusMutation.cs b/NineChronicles.Headless/GraphTypes/ActivationStatusMutation.cs index b8831a30f..83e6440ad 100644 --- a/NineChronicles.Headless/GraphTypes/ActivationStatusMutation.cs +++ b/NineChronicles.Headless/GraphTypes/ActivationStatusMutation.cs @@ -12,7 +12,10 @@ public class ActivationStatusMutation : ObjectGraphType { public ActivationStatusMutation(NineChroniclesNodeService service) { + DeprecationReason = "Since NCIP-15, it doesn't care account activation."; + Field>("activateAccount", + deprecationReason: "Since NCIP-15, it doesn't care account activation.", arguments: new QueryArguments( new QueryArgument> { diff --git a/NineChronicles.Headless/GraphTypes/ActivationStatusQuery.cs b/NineChronicles.Headless/GraphTypes/ActivationStatusQuery.cs index 27ff51632..ef2002563 100644 --- a/NineChronicles.Headless/GraphTypes/ActivationStatusQuery.cs +++ b/NineChronicles.Headless/GraphTypes/ActivationStatusQuery.cs @@ -15,8 +15,11 @@ public class ActivationStatusQuery : ObjectGraphType { public ActivationStatusQuery(StandaloneContext standaloneContext) { + DeprecationReason = "Since NCIP-15, it doesn't care account activation."; + Field>( name: "activated", + deprecationReason: "Since NCIP-15, it doesn't care account activation.", resolve: context => { var service = standaloneContext.NineChroniclesNodeService; @@ -72,6 +75,7 @@ public ActivationStatusQuery(StandaloneContext standaloneContext) Field>( name: "addressActivated", + deprecationReason: "Since NCIP-15, it doesn't care account activation.", arguments: new QueryArguments( new QueryArgument> { diff --git a/NineChronicles.Headless/GraphTypes/StandaloneMutation.cs b/NineChronicles.Headless/GraphTypes/StandaloneMutation.cs index 6916d1a1e..1421a5e47 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneMutation.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneMutation.cs @@ -36,7 +36,8 @@ IConfiguration configuration Field( name: "activationStatus", - resolve: _ => new ActivationStatusMutation(nodeService)); + resolve: _ => new ActivationStatusMutation(nodeService), + deprecationReason: "Since NCIP-15, it doesn't care account activation."); Field( name: "action", diff --git a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs index cb98ed722..03b9e4da3 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs @@ -177,6 +177,7 @@ TransferNCGHistory ToTransferNCGHistory(TxSuccess txSuccess, string? memo) Field>( name: "activationStatus", description: "Check if the provided address is activated.", + deprecationReason: "Since NCIP-15, it doesn't care account activation.", resolve: context => new ActivationStatusQuery(standaloneContext)) .AuthorizeWithLocalPolicyIf(useSecretToken); @@ -355,6 +356,7 @@ TransferNCGHistory ToTransferNCGHistory(TxSuccess txSuccess, string? memo) Field>( name: "activated", + deprecationReason: "Since NCIP-15, it doesn't care account activation.", arguments: new QueryArguments( new QueryArgument> { @@ -389,6 +391,7 @@ TransferNCGHistory ToTransferNCGHistory(TxSuccess txSuccess, string? memo) Field>( name: "activationKeyNonce", + deprecationReason: "Since NCIP-15, it doesn't care account activation.", arguments: new QueryArguments( new QueryArgument> { From 57edd5905d81dc7c3a17cff7c124bf94b0038ee0 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Fri, 30 Jun 2023 11:59:06 +0900 Subject: [PATCH 43/89] Bump lib9c:2023q2-iap --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index c4f36dedd..5d39b5dd7 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit c4f36dedd7193a5694c306c7f8ce58bd34d8e532 +Subproject commit 5d39b5dd783a0786a9181a7701e34b3ad6d3bc52 From 808fc533be9dbaa7e4d1e6c010600894c91e8b5d Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Mon, 3 Jul 2023 15:20:05 +0900 Subject: [PATCH 44/89] Bump lib9c:2023q2-iap --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 5d39b5dd7..575abbd9c 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 5d39b5dd783a0786a9181a7701e34b3ad6d3bc52 +Subproject commit 575abbd9ca0ec3b70fdf04ec6f7e211af824b57f From 97ab751e0ba94f02578e7cf17b367fffb4b26a46 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Mon, 3 Jul 2023 15:42:42 +0900 Subject: [PATCH 45/89] Bump lib9c to 014bff43 --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index c4f36dedd..014bff434 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit c4f36dedd7193a5694c306c7f8ce58bd34d8e532 +Subproject commit 014bff43408542025269079e4c283d57388c3f84 From cf97e45826c2e95cf1c7ffb2986c5166c802505a Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Mon, 3 Jul 2023 18:23:34 +0900 Subject: [PATCH 46/89] Separate garages state query as a partial class --- .../GraphTypes/StateQuery.cs | 28 +----------- .../GraphTypes/StateQueryFields/Garages.cs | 45 +++++++++++++++++++ 2 files changed, 47 insertions(+), 26 deletions(-) create mode 100644 NineChronicles.Headless/GraphTypes/StateQueryFields/Garages.cs diff --git a/NineChronicles.Headless/GraphTypes/StateQuery.cs b/NineChronicles.Headless/GraphTypes/StateQuery.cs index 89a77cefd..21985c16c 100644 --- a/NineChronicles.Headless/GraphTypes/StateQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StateQuery.cs @@ -26,7 +26,7 @@ namespace NineChronicles.Headless.GraphTypes { - public class StateQuery : ObjectGraphType + public partial class StateQuery : ObjectGraphType { public StateQuery() { @@ -542,31 +542,7 @@ public StateQuery() } ); - Field( - "garage", - arguments: new QueryArguments( - new QueryArgument> - { - Name = "address", - Description = "Address to get GARAGE token balance and fungible items" - }, - new QueryArgument> - { - Name = "fungibleItemIds", - Description = "List of fungible item IDs to get stock in garage" - } - ), - resolve: context => - { - var address = context.GetArgument
("address"); - var balance = context.Source.GetBalance(address, Currencies.Garage); - var fungibleItemIdList = context.GetArgument>("fungibleItemIds"); - IEnumerable
fungibleItemAddressList = fungibleItemIdList.Select(fungibleItemId => - Addresses.GetGarageAddress(address, HashDigest.FromString(fungibleItemId))); - var fungibleItemList = context.Source.GetStates(fungibleItemAddressList.ToArray()); - return (balance, fungibleItemList); - } - ); + RegisterGarages(); } } } diff --git a/NineChronicles.Headless/GraphTypes/StateQueryFields/Garages.cs b/NineChronicles.Headless/GraphTypes/StateQueryFields/Garages.cs new file mode 100644 index 000000000..9557a3b70 --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/StateQueryFields/Garages.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using GraphQL; +using GraphQL.Types; +using Lib9c; +using Libplanet; +using Libplanet.Explorer.GraphTypes; +using Nekoyume; +using NineChronicles.Headless.GraphTypes.States; + +namespace NineChronicles.Headless.GraphTypes +{ + public partial class StateQuery + { + private void RegisterGarages() + { + Field( + "garages", + arguments: new QueryArguments( + new QueryArgument> + { + Name = "agentAddr", + Description = "Address to get GARAGE token balance and fungible items" + }, + new QueryArgument>> + { + Name = "fungibleIds", + Description = "List of fungible item IDs to get stock in garage" + } + ), + resolve: context => + { + var agentAddr = context.GetArgument
("agentAddr"); + var balance = context.Source.GetBalance(agentAddr, Currencies.Garage); + var fungibleItemIdList = context.GetArgument>("fungibleIds"); + IEnumerable
fungibleItemAddressList = fungibleItemIdList.Select(fungibleItemId => + Addresses.GetGarageAddress(agentAddr, HashDigest.FromString(fungibleItemId))); + var fungibleItemList = context.Source.GetStates(fungibleItemAddressList.ToArray()); + return (balance, fungibleItemList); + } + ); + } + } +} From 8550f27c504ac1929db55d52621eac226cc1d27a Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Mon, 3 Jul 2023 18:43:55 +0900 Subject: [PATCH 47/89] Accommodate API changes --- .../AccountDelta.cs | 64 ++ .../AccountStateDelta.cs | 112 ++- .../AccountStateDeltaMarshaller.cs | 105 +-- .../RemoteBlockChainStates.cs | 3 + .../Commands/ReplayCommand.Privates.cs | 755 +++++++++--------- .../Commands/ReplayCommand.cs | 25 +- .../Commands/StateCommand.cs | 7 +- .../ActionEvaluationPublisher.cs | 6 +- .../GraphTypes/TransactionHeadlessQuery.cs | 30 +- 9 files changed, 594 insertions(+), 513 deletions(-) create mode 100644 Libplanet.Extensions.ActionEvaluatorCommonComponents/AccountDelta.cs diff --git a/Libplanet.Extensions.ActionEvaluatorCommonComponents/AccountDelta.cs b/Libplanet.Extensions.ActionEvaluatorCommonComponents/AccountDelta.cs new file mode 100644 index 000000000..88f141304 --- /dev/null +++ b/Libplanet.Extensions.ActionEvaluatorCommonComponents/AccountDelta.cs @@ -0,0 +1,64 @@ +using System.Collections.Immutable; +using System.Numerics; +using Bencodex.Types; +using Libplanet.Assets; +using Libplanet.Consensus; +using Libplanet.State; + +namespace Libplanet.Extensions.ActionEvaluatorCommonComponents +{ + public class AccountDelta : IAccountDelta + { + public AccountDelta() + { + States = ImmutableDictionary.Empty; + Fungibles = ImmutableDictionary<(Address, Currency), BigInteger>.Empty; + TotalSupplies = ImmutableDictionary.Empty; + ValidatorSet = null; + } + + public AccountDelta( + IImmutableDictionary statesDelta, + IImmutableDictionary<(Address, Currency), BigInteger> fungiblesDelta, + IImmutableDictionary totalSuppliesDelta, + ValidatorSet? validatorSetDelta) + { + States = statesDelta; + Fungibles = fungiblesDelta; + TotalSupplies = totalSuppliesDelta; + ValidatorSet = validatorSetDelta; + } + + /// + public IImmutableSet
UpdatedAddresses => + StateUpdatedAddresses.Union(FungibleUpdatedAddresses); + + /// + public IImmutableSet
StateUpdatedAddresses => + States.Keys.ToImmutableHashSet(); + + /// + public IImmutableDictionary States { get; } + + /// + public IImmutableSet
FungibleUpdatedAddresses => + Fungibles.Keys.Select(pair => pair.Item1).ToImmutableHashSet(); + + /// + public IImmutableSet<(Address, Currency)> UpdatedFungibleAssets => + Fungibles.Keys.ToImmutableHashSet(); + + /// + public IImmutableDictionary<(Address, Currency), BigInteger> Fungibles { get; } + + /// + public IImmutableSet UpdatedTotalSupplyCurrencies => + TotalSupplies.Keys.ToImmutableHashSet(); + + /// + public IImmutableDictionary TotalSupplies { get; } + + /// + public ValidatorSet? ValidatorSet { get; } + } +} diff --git a/Libplanet.Extensions.ActionEvaluatorCommonComponents/AccountStateDelta.cs b/Libplanet.Extensions.ActionEvaluatorCommonComponents/AccountStateDelta.cs index 8c2d3009d..ea143bff3 100644 --- a/Libplanet.Extensions.ActionEvaluatorCommonComponents/AccountStateDelta.cs +++ b/Libplanet.Extensions.ActionEvaluatorCommonComponents/AccountStateDelta.cs @@ -9,29 +9,25 @@ namespace Libplanet.Extensions.ActionEvaluatorCommonComponents; -public class AccountStateDelta : IAccountStateDelta, IValidatorSupportStateDelta +public class AccountStateDelta : IAccountStateDelta { private IImmutableDictionary _states; - private IImmutableDictionary<(Address, Currency), BigInteger> _balances; + private IImmutableDictionary<(Address, Currency), BigInteger> _fungibles; private IImmutableDictionary _totalSupplies; private ValidatorSet? _validatorSet; + private IAccountDelta _delta; - public IImmutableSet
UpdatedAddresses => _states.Keys.ToImmutableHashSet(); + public IImmutableSet
UpdatedAddresses => _delta.UpdatedAddresses; - public IImmutableSet
StateUpdatedAddresses => _states.Keys.ToImmutableHashSet(); + public IImmutableSet
StateUpdatedAddresses => _delta.StateUpdatedAddresses; -#pragma warning disable LAA1002 - public IImmutableDictionary> UpdatedFungibleAssets => - _balances.GroupBy(kv => kv.Key.Item1).ToImmutableDictionary( - g => g.Key, - g => (IImmutableSet)g.Select(kv => kv.Key.Item2).ToImmutableHashSet() - ); + public IImmutableSet<(Address, Currency)> UpdatedFungibleAssets => _delta.UpdatedFungibleAssets; - public IImmutableDictionary> TotalUpdatedFungibleAssets { get; } +#pragma warning disable LAA1002 + public IImmutableSet<(Address, Currency)> TotalUpdatedFungibleAssets { get; } #pragma warning restore LAA1002 - public IImmutableSet TotalSupplyUpdatedCurrencies => - _totalSupplies.Keys.ToImmutableHashSet(); + public IImmutableSet UpdatedTotalSupplyCurrencies => _delta.UpdatedTotalSupplyCurrencies; public AccountStateGetter StateGetter { get; set; } @@ -53,47 +49,73 @@ public AccountStateDelta() public AccountStateDelta( IImmutableDictionary states, - IImmutableDictionary<(Address, Currency), BigInteger> balances, + IImmutableDictionary<(Address, Currency), BigInteger> fungibles, IImmutableDictionary totalSupplies, ValidatorSet? validatorSet ) { + _delta = new AccountDelta( + states, + fungibles, + totalSupplies, + validatorSet); _states = states; - _balances = balances; + _fungibles = fungibles; _totalSupplies = totalSupplies; _validatorSet = validatorSet; } - public AccountStateDelta(Dictionary states, List balances, Dictionary totalSupplies, IValue validatorSet) + public AccountStateDelta(Dictionary states, List fungibles, List totalSupplies, IValue validatorSet) { // This assumes `states` consists of only Binary keys: - _states = states.ToImmutableDictionary( - kv => new Address(kv.Key), - kv => kv.Value - ); - - _balances = balances.Cast().ToImmutableDictionary( - record => (new Address(((Binary)record["address"]).ByteArray), - new Currency((Dictionary)record["currency"])), - record => (BigInteger)(Integer)record["amount"] - ); + _states = states + .ToImmutableDictionary( + kv => new Address(((Binary)kv.Key).ByteArray), + kv => kv.Value); + + _fungibles = fungibles + .Cast() + .Select(dict => + new KeyValuePair<(Address, Currency), BigInteger>( + ( + new Address(((Binary)dict["address"]).ByteArray), + new Currency(dict["currency"]) + ), + new BigInteger((Integer)dict["amount"]) + )) + .ToImmutableDictionary(); // This assumes `totalSupplies` consists of only Binary keys: - _totalSupplies = totalSupplies.ToImmutableDictionary( - kv => new Currency(new Codec().Decode((Binary)kv.Key)), - kv => (BigInteger)(Integer)kv.Value - ); - - _validatorSet = new ValidatorSet(validatorSet); + _totalSupplies = totalSupplies + .Cast() + .Select(dict => + new KeyValuePair( + new Currency(dict["currency"]), + new BigInteger((Integer)dict["amount"]))) + .ToImmutableDictionary(); + + _validatorSet = validatorSet is Null + ? null + : new ValidatorSet(validatorSet); + + _delta = new AccountDelta( + _states, + _fungibles, + _totalSupplies, + _validatorSet); } public AccountStateDelta(IValue serialized) + : this((Dictionary)serialized) + { + } + + public AccountStateDelta(Dictionary dict) : this( - (Dictionary)((Dictionary)serialized)["states"], - (List)((Dictionary)serialized)["balances"], - (Dictionary)((Dictionary)serialized)["totalSupplies"], - ((Dictionary)serialized)["validatorSet"] - ) + (Dictionary)dict["states"], + (List)dict["balances"], + (List)dict["totalSupplies"], + dict["validatorSet"]) { } @@ -102,6 +124,8 @@ public AccountStateDelta(byte[] bytes) { } + public IAccountDelta Delta => _delta; + public IValue? GetState(Address address) => _states.ContainsKey(address) ? _states[address] @@ -111,7 +135,7 @@ public AccountStateDelta(byte[] bytes) addresses.Select(GetState).ToArray(); public IAccountStateDelta SetState(Address address, IValue state) => - new AccountStateDelta(_states.SetItem(address, state), _balances, _totalSupplies, _validatorSet) + new AccountStateDelta(_states.SetItem(address, state), _fungibles, _totalSupplies, _validatorSet) { StateGetter = StateGetter, BalanceGetter = BalanceGetter, @@ -121,7 +145,7 @@ public IAccountStateDelta SetState(Address address, IValue state) => public FungibleAssetValue GetBalance(Address address, Currency currency) { - if (!_balances.TryGetValue((address, currency), out BigInteger rawValue)) + if (!_fungibles.TryGetValue((address, currency), out BigInteger rawValue)) { return BalanceGetter(address, currency); } @@ -177,7 +201,7 @@ public IAccountStateDelta MintAsset( return new AccountStateDelta( _states, - _balances.SetItem( + _fungibles.SetItem( (recipient, value.Currency), nextAmount.RawValue ), @@ -194,7 +218,7 @@ public IAccountStateDelta MintAsset( return new AccountStateDelta( _states, - _balances.SetItem( + _fungibles.SetItem( (recipient, value.Currency), nextAmount.RawValue ), @@ -234,7 +258,7 @@ public IAccountStateDelta TransferAsset( Currency currency = value.Currency; FungibleAssetValue senderRemains = senderBalance - value; FungibleAssetValue recipientRemains = GetBalance(recipient, currency) + value; - var balances = _balances + var balances = _fungibles .SetItem((sender, currency), senderRemains.RawValue) .SetItem((recipient, currency), recipientRemains.RawValue); return new AccountStateDelta(_states, balances, _totalSupplies, _validatorSet) @@ -272,7 +296,7 @@ public IAccountStateDelta BurnAsset( FungibleAssetValue nextValue = balance - value; return new AccountStateDelta( _states, - _balances.SetItem( + _fungibles.SetItem( (owner, currency), nextValue.RawValue ), @@ -300,7 +324,7 @@ public IAccountStateDelta SetValidator(Validator validator) { return new AccountStateDelta( _states, - _balances, + _fungibles, _totalSupplies, GetValidatorSet().Update(validator) ) diff --git a/Libplanet.Extensions.ActionEvaluatorCommonComponents/AccountStateDeltaMarshaller.cs b/Libplanet.Extensions.ActionEvaluatorCommonComponents/AccountStateDeltaMarshaller.cs index 4605b897b..2e5d8de5f 100644 --- a/Libplanet.Extensions.ActionEvaluatorCommonComponents/AccountStateDeltaMarshaller.cs +++ b/Libplanet.Extensions.ActionEvaluatorCommonComponents/AccountStateDeltaMarshaller.cs @@ -16,94 +16,37 @@ public static byte[] Serialize(this IAccountStateDelta value) return Codec.Encode(Marshal(value)); } - public static IEnumerable Marshal(IEnumerable deltas) + public static IEnumerable Marshal(IEnumerable stateDeltas) { - IImmutableDictionary updatedStates = ImmutableDictionary.Empty; - IImmutableDictionary> updatedFungibleAssets = ImmutableDictionary>.Empty; - IImmutableSet totalSupplyUpdatedCurrencies = ImmutableHashSet.Empty; - foreach (var value in deltas) + foreach (var stateDelta in stateDeltas) { - updatedStates = updatedStates.SetItems(value.StateUpdatedAddresses.Select(addr => - new KeyValuePair(addr, value.GetState(addr)))); - updatedFungibleAssets = updatedFungibleAssets.SetItems(value.UpdatedFungibleAssets); - totalSupplyUpdatedCurrencies = totalSupplyUpdatedCurrencies.Union(value.TotalSupplyUpdatedCurrencies); - var state = new Dictionary( - updatedStates.Select(pair => new KeyValuePair((Binary)pair.Key.ByteArray, pair.Value)) - ); - var balance = new Bencodex.Types.List( -#pragma warning disable LAA1002 - updatedFungibleAssets.SelectMany(ua => -#pragma warning restore LAA1002 - ua.Value.Select(c => - { - FungibleAssetValue b = value.GetBalance(ua.Key, c); - return new Bencodex.Types.Dictionary(new[] - { - new KeyValuePair((Text) "address", (Binary) ua.Key.ByteArray), - new KeyValuePair((Text) "currency", c.Serialize()), - new KeyValuePair((Text) "amount", (Integer) b.RawValue), - }); - } - ) - ).Cast() - ); - var totalSupply = new Dictionary( - totalSupplyUpdatedCurrencies.Select(currency => - new KeyValuePair( - (Binary)Codec.Encode(currency.Serialize()), - (Integer)value.GetTotalSupply(currency).RawValue))); - - var bdict = new Dictionary(new[] - { - new KeyValuePair((Text) "states", state), - new KeyValuePair((Text) "balances", balance), - new KeyValuePair((Text) "totalSupplies", totalSupply), - new KeyValuePair((Text) "validatorSet", value.GetValidatorSet().Bencoded), - }); - + var bdict = Marshal(stateDelta); yield return bdict; } } - public static Dictionary Marshal(IAccountStateDelta value) + public static Dictionary Marshal(IAccountStateDelta stateDelta) { - var state = new Dictionary( - value.UpdatedAddresses.Where(addr => value.GetState(addr) is not null).Select(addr => new KeyValuePair( - (Binary)addr.ToByteArray(), - value.GetState(addr) - )) - ); - var balance = new Bencodex.Types.List( -#pragma warning disable LAA1002 - value.UpdatedFungibleAssets.SelectMany(ua => -#pragma warning restore LAA1002 - ua.Value.Select(c => - { - FungibleAssetValue b = value.GetBalance(ua.Key, c); - return new Bencodex.Types.Dictionary(new[] - { - new KeyValuePair((Text) "address", (Binary) ua.Key.ByteArray), - new KeyValuePair((Text) "currency", c.Serialize()), - new KeyValuePair((Text) "amount", (Integer) b.RawValue), - }); - } - ) - ).Cast() - ); - var totalSupply = new Dictionary( - value.TotalSupplyUpdatedCurrencies.Select(currency => - new KeyValuePair( - (Binary)new Codec().Encode(currency.Serialize()), - (Integer)value.GetTotalSupply(currency).RawValue))); - - var bdict = new Dictionary(new[] - { - new KeyValuePair((Text) "states", state), - new KeyValuePair((Text) "balances", balance), - new KeyValuePair((Text) "totalSupplies", totalSupply), - new KeyValuePair((Text) "validatorSet", value.GetValidatorSet().Bencoded), - }); - + var state = new Dictionary(stateDelta.Delta.States.Select( + kv => new KeyValuePair( + new Binary(kv.Key.ByteArray), + kv.Value))); + var balance = new List(stateDelta.Delta.Fungibles.Select( + kv => Dictionary.Empty + .Add("address", new Binary(kv.Key.Item1.ByteArray)) + .Add("currency", kv.Key.Item2.Serialize()) + .Add("amount", new Integer(kv.Value)))); + var totalSupply = new List(stateDelta.Delta.TotalSupplies.Select( + kv => Dictionary.Empty + .Add("currency", kv.Key.Serialize()) + .Add("amount", new Integer(kv.Value)))); + var bdict = Dictionary.Empty + .Add("states", state) + .Add("balances", balance) + .Add("totalSupplies", totalSupply) + .Add("validatorSet", stateDelta.Delta.ValidatorSet is { } validatorSet + ? validatorSet.Bencoded + : Null.Value); return bdict; } diff --git a/Libplanet.Extensions.RemoteBlockChainStates/RemoteBlockChainStates.cs b/Libplanet.Extensions.RemoteBlockChainStates/RemoteBlockChainStates.cs index 618c535cc..772b0e926 100644 --- a/Libplanet.Extensions.RemoteBlockChainStates/RemoteBlockChainStates.cs +++ b/Libplanet.Extensions.RemoteBlockChainStates/RemoteBlockChainStates.cs @@ -24,6 +24,9 @@ public RemoteBlockChainStates(Uri explorerEndpoint) new GraphQLHttpClient(_explorerEndpoint, new SystemTextJsonSerializer()); } + public IValue? GetState(Address address, BlockHash? offset) => + GetStates(new[] { address }, offset).First(); + public IReadOnlyList GetStates(IReadOnlyList
addresses, BlockHash? offset) { var response = _graphQlHttpClient.SendQueryAsync( diff --git a/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs b/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs index 4f95ce648..d375fef34 100644 --- a/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs +++ b/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; +using System.Diagnostics.Contracts; using System.Linq; using System.Numerics; using System.Security.Cryptography; @@ -22,119 +24,134 @@ namespace NineChronicles.Headless.Executable.Commands public partial class ReplayCommand : CoconaLiteConsoleAppBase { /// - /// Almost duplicate https://github.com/planetarium/libplanet/blob/main/Libplanet/Action/AccountStateDeltaImpl.cs. + /// Almost duplicate https://github.com/planetarium/libplanet/blob/main/Libplanet/State/AccountStateDelta.cs. /// - private class AccountStateDeltaImpl : IAccountStateDelta + [Pure] + private sealed class AccountStateDelta : IAccountStateDelta { - public IImmutableSet
UpdatedAddresses => - UpdatedStates.Keys.ToImmutableHashSet() - .Union(UpdatedFungibles - .Select(kv => kv.Key.Item1)); - - public IImmutableSet
StateUpdatedAddresses => - UpdatedStates.Keys.ToImmutableHashSet(); + /// + /// Creates a null state delta from the given . + /// + /// A view to the “epoch” states. + /// A view to the “epoch” asset balances. + /// + /// A view to the “epoch” total supplies of + /// currencies. + /// A view to the “epoch” validator + /// set. + private AccountStateDelta( + AccountStateGetter accountStateGetter, + AccountBalanceGetter accountBalanceGetter, + TotalSupplyGetter totalSupplyGetter, + ValidatorSetGetter validatorSetGetter) + { + Delta = new AccountDelta(); + StateGetter = accountStateGetter; + BalanceGetter = accountBalanceGetter; + TotalSupplyGetter = totalSupplyGetter; + ValidatorSetGetter = validatorSetGetter; + TotalUpdatedFungibles = ImmutableDictionary<(Address, Currency), BigInteger>.Empty; + } - public IImmutableDictionary> - UpdatedFungibleAssets => - UpdatedFungibles - .GroupBy(kv => kv.Key.Item1) - .ToImmutableDictionary( - g => g.Key, - g => - (IImmutableSet)g - .Select(kv => kv.Key.Item2) - .ToImmutableHashSet()); + /// + public IAccountDelta Delta { get; private set; } - public IImmutableDictionary> TotalUpdatedFungibleAssets { get; } = new Dictionary>().ToImmutableDictionary(); + /// + [Pure] + public IImmutableSet
UpdatedAddresses => + Delta.UpdatedAddresses; - public IImmutableSet TotalSupplyUpdatedCurrencies => - UpdatedTotalSupply.Keys.ToImmutableHashSet(); + /// + public IImmutableSet
StateUpdatedAddresses => + Delta.StateUpdatedAddresses; - protected AccountStateGetter StateGetter { get; set; } + /// + public IImmutableSet<(Address, Currency)> UpdatedFungibleAssets => + Delta.UpdatedFungibleAssets; - protected AccountBalanceGetter BalanceGetter { get; set; } + /// + public IImmutableSet<(Address, Currency)> TotalUpdatedFungibleAssets => + TotalUpdatedFungibles.Keys.ToImmutableHashSet(); - protected TotalSupplyGetter TotalSupplyGetter { get; set; } + [Pure] + public IImmutableSet UpdatedTotalSupplyCurrencies => + Delta.UpdatedTotalSupplyCurrencies; - protected ValidatorSetGetter ValidatorSetGetter { get; set; } + public IImmutableDictionary<(Address, Currency), BigInteger> TotalUpdatedFungibles + { get; private set; } - protected IImmutableDictionary UpdatedStates { get; set; } + private AccountStateGetter StateGetter { get; set; } - protected IImmutableDictionary<(Address, Currency), BigInteger> UpdatedFungibles { get; set; } + private AccountBalanceGetter BalanceGetter { get; set; } - protected IImmutableDictionary UpdatedTotalSupply { get; set; } + private TotalSupplyGetter TotalSupplyGetter { get; set; } - protected ValidatorSet? UpdatedValidatorSet { get; set; } = null; + private ValidatorSetGetter ValidatorSetGetter { get; set; } - public AccountStateDeltaImpl( - AccountStateGetter accountStateGetter, - AccountBalanceGetter accountBalanceGetter, - TotalSupplyGetter totalSupplyGetter, - ValidatorSetGetter validatorSetGetter) + /// + [Pure] + public IValue? GetState(Address address) { - StateGetter = accountStateGetter; - BalanceGetter = accountBalanceGetter; - TotalSupplyGetter = totalSupplyGetter; - ValidatorSetGetter = validatorSetGetter; - UpdatedStates = ImmutableDictionary.Empty; - UpdatedFungibles = ImmutableDictionary<(Address, Currency), BigInteger>.Empty; - UpdatedTotalSupply = ImmutableDictionary.Empty; + IValue? state = GetStates(new[] { address })[0]; + return state; } - public IValue? GetState(Address address) => - UpdatedStates.TryGetValue(address, out IValue? value) - ? value - : StateGetter(new[] { address })[0]; - + /// + [Pure] public IReadOnlyList GetStates(IReadOnlyList
addresses) { int length = addresses.Count; IValue?[] values = new IValue?[length]; - var notFound = new List
(length); + var notFoundIndices = new List(length); for (int i = 0; i < length; i++) { Address address = addresses[i]; - if (UpdatedStates.TryGetValue(address, out IValue? v)) + if (Delta.States.TryGetValue(address, out IValue? updatedValue)) { - values[i] = v; - continue; + values[i] = updatedValue; + } + else + { + notFoundIndices.Add(i); } - - notFound.Add(address); } - IReadOnlyList restValues = StateGetter(notFound); - for (int i = 0, j = 0; i < length && j < notFound.Count; i++) + if (notFoundIndices.Count > 0) { - if (addresses[i].Equals(notFound[j])) + IReadOnlyList restValues = StateGetter( + notFoundIndices.Select(index => addresses[index]).ToArray()); + foreach ((var v, var i) in notFoundIndices.Select((v, i) => (v, i))) { - values[i] = restValues[j]; - j++; + values[v] = restValues[i]; } } return values; } - public FungibleAssetValue GetBalance( - Address address, - Currency currency) => - GetBalance(address, currency, UpdatedFungibles); + /// + [Pure] + public IAccountStateDelta SetState(Address address, IValue state) => + UpdateStates(Delta.States.SetItem(address, state)); + + /// + [Pure] + public FungibleAssetValue GetBalance(Address address, Currency currency) => + GetBalance(address, currency, Delta.Fungibles); + /// + [Pure] public FungibleAssetValue GetTotalSupply(Currency currency) { if (!currency.TotalSupplyTrackable) { - // throw TotalSupplyNotTrackableException.WithDefaultMessage(currency); - var msg = - $"The total supply value of the currency {currency} is not trackable because it" - + " is a legacy untracked currency which might have been established before" - + " the introduction of total supply tracking support."; - throw new TotalSupplyNotTrackableException(msg, currency); + throw new TotalSupplyNotTrackableException( + $"Given currency {currency} is not trackable for its total supply", + currency); } // Return dirty state if it exists. - if (UpdatedTotalSupply.TryGetValue(currency, out BigInteger totalSupplyValue)) + if (Delta.TotalSupplies.TryGetValue(currency, out BigInteger totalSupplyValue)) { return FungibleAssetValue.FromRawValue(currency, totalSupplyValue); } @@ -142,9 +159,13 @@ public FungibleAssetValue GetTotalSupply(Currency currency) return TotalSupplyGetter(currency); } - public IAccountStateDelta SetState(Address address, IValue state) => - UpdateStates(UpdatedStates.SetItem(address, state)); + /// + [Pure] + public ValidatorSet GetValidatorSet() => + Delta.ValidatorSet ?? ValidatorSetGetter(); + /// + [Pure] public IAccountStateDelta MintAsset( IActionContext context, Address recipient, FungibleAssetValue value) { @@ -167,6 +188,8 @@ public IAccountStateDelta MintAsset( } FungibleAssetValue balance = GetBalance(recipient, currency); + (Address, Currency) assetKey = (recipient, currency); + BigInteger rawBalance = (balance + value).RawValue; if (currency.TotalSupplyTrackable) { @@ -174,62 +197,37 @@ public IAccountStateDelta MintAsset( if (currency.MaximumSupply < currentTotalSupply + value) { var msg = $"The amount {value} attempted to be minted added to the current" - + $" total supply of {currentTotalSupply} exceeds the" - + $" maximum allowed supply of {currency.MaximumSupply}."; + + $" total supply of {currentTotalSupply} exceeds the" + + $" maximum allowed supply of {currency.MaximumSupply}."; throw new SupplyOverflowException(msg, value); } return UpdateFungibleAssets( - UpdatedFungibles.SetItem((recipient, currency), (balance + value).RawValue), - UpdatedTotalSupply.SetItem(currency, (currentTotalSupply + value).RawValue) + Delta.Fungibles.SetItem(assetKey, rawBalance), + TotalUpdatedFungibles.SetItem(assetKey, rawBalance), + Delta.TotalSupplies.SetItem(currency, (currentTotalSupply + value).RawValue) ); } return UpdateFungibleAssets( - UpdatedFungibles.SetItem((recipient, currency), (balance + value).RawValue) + Delta.Fungibles.SetItem(assetKey, rawBalance), + TotalUpdatedFungibles.SetItem(assetKey, rawBalance) ); } + /// + [Pure] public IAccountStateDelta TransferAsset( IActionContext context, Address sender, Address recipient, FungibleAssetValue value, - bool allowNegativeBalance = false) - { - if (value.Sign <= 0) - { - throw new ArgumentOutOfRangeException( - nameof(value), - "The value to transfer has to be greater than zero." - ); - } - - Currency currency = value.Currency; - FungibleAssetValue senderBalance = GetBalance(sender, currency); - - if (!allowNegativeBalance && senderBalance < value) - { - var msg = $"The account {sender}'s balance of {currency} is insufficient to " + - $"transfer: {senderBalance} < {value}."; - throw new InsufficientBalanceException(msg, sender, senderBalance); - } - - IImmutableDictionary<(Address, Currency), BigInteger> updatedFungibleAssets = - UpdatedFungibles - .SetItem((sender, currency), (senderBalance - value).RawValue); - - FungibleAssetValue recipientBalance = GetBalance( - recipient, - currency, - updatedFungibleAssets); - - return UpdateFungibleAssets( - updatedFungibleAssets - .SetItem((recipient, currency), (recipientBalance + value).RawValue) - ); - } + bool allowNegativeBalance = false) => context.BlockProtocolVersion > 0 + ? TransferAssetV1(sender, recipient, value, allowNegativeBalance) + : TransferAssetV0(sender, recipient, value, allowNegativeBalance); + /// + [Pure] public IAccountStateDelta BurnAsset( IActionContext context, Address owner, FungibleAssetValue value) { @@ -247,7 +245,7 @@ public IAccountStateDelta BurnAsset( if (!currency.AllowsToMint(context.Signer)) { msg = $"The account {context.Signer} has no permission to burn assets of " + - $"the currency {currency}."; + $"the currency {currency}."; throw new CurrencyPermissionException(msg, context.Signer, currency); } @@ -256,70 +254,89 @@ public IAccountStateDelta BurnAsset( if (balance < value) { msg = $"The account {owner}'s balance of {currency} is insufficient to burn: " + - $"{balance} < {value}."; + $"{balance} < {value}."; throw new InsufficientBalanceException(msg, owner, balance); } + (Address, Currency) assetKey = (owner, currency); + BigInteger rawBalance = (balance - value).RawValue; if (currency.TotalSupplyTrackable) { return UpdateFungibleAssets( - UpdatedFungibles.SetItem((owner, currency), (balance - value).RawValue), - UpdatedTotalSupply.SetItem( + Delta.Fungibles.SetItem(assetKey, rawBalance), + TotalUpdatedFungibles.SetItem(assetKey, rawBalance), + Delta.TotalSupplies.SetItem( currency, (GetTotalSupply(currency) - value).RawValue) ); } return UpdateFungibleAssets( - UpdatedFungibles.SetItem((owner, currency), (balance - value).RawValue) + Delta.Fungibles.SetItem(assetKey, rawBalance), + TotalUpdatedFungibles.SetItem(assetKey, rawBalance) ); } - public IImmutableDictionary GetUpdatedStates() => - StateUpdatedAddresses.Select(address => - new KeyValuePair( - address, - GetState(address) - ) - ).ToImmutableDictionary(); - - public IImmutableDictionary<(Address, Currency), FungibleAssetValue> - GetUpdatedBalances() => - UpdatedFungibleAssets.SelectMany(kv => - kv.Value.Select(currency => - new KeyValuePair<(Address, Currency), FungibleAssetValue>( - (kv.Key, currency), - GetBalance(kv.Key, currency) - ) - ) - ).ToImmutableDictionary(); - - public IImmutableDictionary - GetUpdatedTotalSupplies() => - TotalSupplyUpdatedCurrencies.Select(currency => - new KeyValuePair( - currency, - GetTotalSupply(currency))) - .ToImmutableDictionary(); - - public IImmutableDictionary GetUpdatedRawStates() => - GetUpdatedStates() - .Select(pair => - new KeyValuePair( - ToStateKey(pair.Key), - pair.Value)) - .Union( - GetUpdatedBalances().Select(pair => - new KeyValuePair( - ToFungibleAssetKey(pair.Key), - (Integer)pair.Value.RawValue))) - .Union( - GetUpdatedTotalSupplies().Select(pair => - new KeyValuePair( - ToTotalSupplyKey(pair.Key), - (Integer)pair.Value.RawValue))).ToImmutableDictionary(); - - protected virtual FungibleAssetValue GetBalance( + /// + [Pure] + public IAccountStateDelta SetValidator(Validator validator) + { + return UpdateValidatorSet(GetValidatorSet().Update(validator)); + } + + /// + /// Creates a null state delta from given . + /// + /// The previous to use as + /// a basis. + /// A null state delta created from . + /// + internal static IAccountStateDelta Create(IAccountState previousState) + { + return new AccountStateDelta( + previousState.GetStates, + previousState.GetBalance, + previousState.GetTotalSupply, + previousState.GetValidatorSet); + } + + /// + /// Creates a null state delta while inheriting s + /// total updated fungibles. + /// + /// The previous to use. + /// A null state delta that is of the same type as . + /// + /// Thrown if given + /// is not . + /// + /// + /// This inherits 's + /// . + /// + internal static IAccountStateDelta Flush( + IAccountStateDelta stateDelta) + { + if (stateDelta is AccountStateDelta impl) + { + return new AccountStateDelta( + stateDelta.GetStates, + stateDelta.GetBalance, + stateDelta.GetTotalSupply, + stateDelta.GetValidatorSet) + { + TotalUpdatedFungibles = impl.TotalUpdatedFungibles, + }; + } + else + { + throw new ArgumentException( + $"Unknown type for {nameof(stateDelta)}: {stateDelta.GetType()}"); + } + } + + [Pure] + private FungibleAssetValue GetBalance( Address address, Currency currency, IImmutableDictionary<(Address, Currency), BigInteger> balances) => @@ -327,94 +344,221 @@ protected virtual FungibleAssetValue GetBalance( ? FungibleAssetValue.FromRawValue(currency, balance) : BalanceGetter(address, currency); - protected virtual AccountStateDeltaImpl UpdateStates( + [Pure] + private AccountStateDelta UpdateStates( IImmutableDictionary updatedStates ) => - new AccountStateDeltaImpl( + new AccountStateDelta( StateGetter, BalanceGetter, TotalSupplyGetter, ValidatorSetGetter) { - UpdatedStates = updatedStates, - UpdatedFungibles = UpdatedFungibles, - UpdatedTotalSupply = UpdatedTotalSupply, - UpdatedValidatorSet = UpdatedValidatorSet, + Delta = new AccountDelta( + updatedStates, + Delta.Fungibles, + Delta.TotalSupplies, + Delta.ValidatorSet), + TotalUpdatedFungibles = TotalUpdatedFungibles, }; - protected virtual AccountStateDeltaImpl UpdateFungibleAssets( + [Pure] + private AccountStateDelta UpdateFungibleAssets( IImmutableDictionary<(Address, Currency), BigInteger> updatedFungibleAssets, + IImmutableDictionary<(Address, Currency), BigInteger> totalUpdatedFungibles + ) => + UpdateFungibleAssets( + updatedFungibleAssets, + totalUpdatedFungibles, + Delta.TotalSupplies); + + [Pure] + private AccountStateDelta UpdateFungibleAssets( + IImmutableDictionary<(Address, Currency), BigInteger> updatedFungibleAssets, + IImmutableDictionary<(Address, Currency), BigInteger> totalUpdatedFungibles, IImmutableDictionary updatedTotalSupply ) => - new AccountStateDeltaImpl( + new AccountStateDelta( StateGetter, BalanceGetter, TotalSupplyGetter, ValidatorSetGetter) { - UpdatedStates = UpdatedStates, - UpdatedFungibles = updatedFungibleAssets, - UpdatedTotalSupply = updatedTotalSupply, - UpdatedValidatorSet = UpdatedValidatorSet, + Delta = new AccountDelta( + Delta.States, + updatedFungibleAssets, + updatedTotalSupply, + Delta.ValidatorSet), + TotalUpdatedFungibles = totalUpdatedFungibles, }; - protected virtual AccountStateDeltaImpl UpdateFungibleAssets( - IImmutableDictionary<(Address, Currency), BigInteger> updatedFungibleAssets - ) => - UpdateFungibleAssets(updatedFungibleAssets, UpdatedTotalSupply); - - public static string ToStateKey(Address address) => - address.ToHex().ToLowerInvariant(); - - public static string ToFungibleAssetKey(Address address, Currency currency) => - "_" + address.ToHex().ToLowerInvariant() + - "_" + ByteUtil.Hex(currency.Hash.ByteArray).ToLowerInvariant(); - - public static string ToFungibleAssetKey((Address, Currency) pair) => - ToFungibleAssetKey(pair.Item1, pair.Item2); - - public static string ToTotalSupplyKey(Currency currency) => - "__" + ByteUtil.Hex(currency.Hash.ByteArray).ToLowerInvariant(); - - protected virtual AccountStateDeltaImpl UpdateValidatorSet( + [Pure] + private AccountStateDelta UpdateValidatorSet( ValidatorSet updatedValidatorSet ) => - new AccountStateDeltaImpl( + new AccountStateDelta( StateGetter, BalanceGetter, TotalSupplyGetter, ValidatorSetGetter) { - UpdatedStates = UpdatedStates, - UpdatedFungibles = UpdatedFungibles, - UpdatedTotalSupply = UpdatedTotalSupply, - UpdatedValidatorSet = updatedValidatorSet, + Delta = new AccountDelta( + Delta.States, + Delta.Fungibles, + Delta.TotalSupplies, + updatedValidatorSet), + TotalUpdatedFungibles = TotalUpdatedFungibles, }; - public virtual ValidatorSet GetValidatorSet() => - UpdatedValidatorSet ?? ValidatorSetGetter(); + [Pure] + private IAccountStateDelta TransferAssetV0( + Address sender, + Address recipient, + FungibleAssetValue value, + bool allowNegativeBalance = false) + { + if (value.Sign <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(value), + "The value to transfer has to be greater than zero." + ); + } + + Currency currency = value.Currency; + FungibleAssetValue senderBalance = GetBalance(sender, currency); + FungibleAssetValue recipientBalance = GetBalance(recipient, currency); - public IAccountStateDelta SetValidator(Validator validator) + if (!allowNegativeBalance && senderBalance < value) + { + var msg = $"The account {sender}'s balance of {currency} is insufficient to " + + $"transfer: {senderBalance} < {value}."; + throw new InsufficientBalanceException(msg, sender, senderBalance); + } + + return UpdateFungibleAssets( + Delta.Fungibles + .SetItem((sender, currency), (senderBalance - value).RawValue) + .SetItem((recipient, currency), (recipientBalance + value).RawValue), + TotalUpdatedFungibles + .SetItem((sender, currency), (senderBalance - value).RawValue) + .SetItem((recipient, currency), (recipientBalance + value).RawValue) + ); + } + + [Pure] + private IAccountStateDelta TransferAssetV1( + Address sender, + Address recipient, + FungibleAssetValue value, + bool allowNegativeBalance = false) { - Log.Debug( - "Update validator {PublicKey} {Power} to validator set", - validator.PublicKey, - validator.Power); - return UpdateValidatorSet(GetValidatorSet().Update(validator)); + if (value.Sign <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(value), + "The value to transfer has to be greater than zero." + ); + } + + Currency currency = value.Currency; + FungibleAssetValue senderBalance = GetBalance(sender, currency); + + if (!allowNegativeBalance && senderBalance < value) + { + var msg = $"The account {sender}'s balance of {currency} is insufficient to " + + $"transfer: {senderBalance} < {value}."; + throw new InsufficientBalanceException(msg, sender, senderBalance); + } + + (Address, Currency) senderAssetKey = (sender, currency); + BigInteger senderRawBalance = (senderBalance - value).RawValue; + + IImmutableDictionary<(Address, Currency), BigInteger> updatedFungibleAssets = + Delta.Fungibles.SetItem(senderAssetKey, senderRawBalance); + IImmutableDictionary<(Address, Currency), BigInteger> totalUpdatedFungibles = + TotalUpdatedFungibles.SetItem(senderAssetKey, senderRawBalance); + + FungibleAssetValue recipientBalance = GetBalance( + recipient, + currency, + updatedFungibleAssets); + (Address, Currency) recipientAssetKey = (recipient, currency); + BigInteger recipientRawBalance = (recipientBalance + value).RawValue; + + return UpdateFungibleAssets( + updatedFungibleAssets.SetItem(recipientAssetKey, recipientRawBalance), + totalUpdatedFungibles.SetItem(recipientAssetKey, recipientRawBalance) + ); } } + /// + /// Almost duplicate https://github.com/planetarium/libplanet/blob/main/Libplanet/State/AccountDelta.cs. + /// + private sealed class AccountDelta : IAccountDelta + { + internal AccountDelta() + { + States = ImmutableDictionary.Empty; + Fungibles = ImmutableDictionary<(Address, Currency), BigInteger>.Empty; + TotalSupplies = ImmutableDictionary.Empty; + ValidatorSet = null; + } + + internal AccountDelta( + IImmutableDictionary statesDelta, + IImmutableDictionary<(Address, Currency), BigInteger> fungiblesDelta, + IImmutableDictionary totalSuppliesDelta, + ValidatorSet? validatorSetDelta) + { + States = statesDelta; + Fungibles = fungiblesDelta; + TotalSupplies = totalSuppliesDelta; + ValidatorSet = validatorSetDelta; + } + + /// + public IImmutableSet
UpdatedAddresses => + StateUpdatedAddresses.Union(FungibleUpdatedAddresses); + + /// + public IImmutableSet
StateUpdatedAddresses => + States.Keys.ToImmutableHashSet(); + + /// + public IImmutableDictionary States { get; } + + /// + public IImmutableSet
FungibleUpdatedAddresses => + Fungibles.Keys.Select(pair => pair.Item1).ToImmutableHashSet(); + + /// + public IImmutableSet<(Address, Currency)> UpdatedFungibleAssets => + Fungibles.Keys.ToImmutableHashSet(); + + /// + public IImmutableDictionary<(Address, Currency), BigInteger> Fungibles { get; } + + /// + public IImmutableSet UpdatedTotalSupplyCurrencies => + TotalSupplies.Keys.ToImmutableHashSet(); + + /// + public IImmutableDictionary TotalSupplies { get; } + + /// + public ValidatorSet? ValidatorSet { get; } + } + /// /// Almost duplicate https://github.com/planetarium/libplanet/blob/main/Libplanet/Action/ActionContext.cs. /// private sealed class ActionContext : IActionContext { private readonly int _randomSeed; - private readonly ITrie? _previousBlockStatesTrie; - private HashDigest? _previousStateRootHash; public ActionContext( - BlockHash? genesisHash, Address signer, TxId? txid, Address miner, @@ -422,11 +566,8 @@ public ActionContext( int blockProtocolVersion, IAccountStateDelta previousStates, int randomSeed, - bool rehearsal = false, - ITrie? previousBlockStatesTrie = null, - bool blockAction = false) + bool rehearsal = false) { - GenesisHash = genesisHash; Signer = signer; TxId = txid; Miner = miner; @@ -436,12 +577,8 @@ public ActionContext( PreviousStates = previousStates; Random = new Random(randomSeed); _randomSeed = randomSeed; - _previousBlockStatesTrie = previousBlockStatesTrie; - BlockAction = blockAction; } - public BlockHash? GenesisHash { get; } - public Address Signer { get; } public TxId? TxId { get; } @@ -458,16 +595,7 @@ public ActionContext( public IRandom Random { get; } - public HashDigest? PreviousStateRootHash => - _previousStateRootHash ??= _previousBlockStatesTrie is null - ? null - : Set( - _previousBlockStatesTrie!, - GetUpdatedRawStates(PreviousStates)) - .Commit() - .Hash; - - public bool BlockAction { get; } + public bool BlockAction => TxId is null; public void PutLog(string log) { @@ -480,7 +608,6 @@ public void UseGas(long gas) public IActionContext GetUnconsumedContext() => new ActionContext( - GenesisHash, Signer, TxId, Miner, @@ -488,69 +615,11 @@ public IActionContext GetUnconsumedContext() => BlockProtocolVersion, PreviousStates, _randomSeed, - Rehearsal, - _previousBlockStatesTrie, - BlockAction); + Rehearsal); public long GasUsed() => 0; public long GasLimit() => 0; - - private IImmutableDictionary GetUpdatedRawStates( - IAccountStateDelta delta) - { - if (delta is not AccountStateDeltaImpl impl) - { - return ImmutableDictionary.Empty; - } - - return impl.GetUpdatedStates() - .Select(pair => - new KeyValuePair( - AccountStateDeltaImpl.ToStateKey(pair.Key), - pair.Value)) - .Union( - impl.GetUpdatedBalances().Select(pair => - new KeyValuePair( - AccountStateDeltaImpl.ToFungibleAssetKey(pair.Key), - (Integer)pair.Value.RawValue))) - .Union( - impl.GetUpdatedTotalSupplies().Select(pair => - new KeyValuePair( - AccountStateDeltaImpl.ToTotalSupplyKey(pair.Key), - (Integer)pair.Value.RawValue))).ToImmutableDictionary(); - } - - private ITrie Set(ITrie trie, IEnumerable> pairs) - => Set( - trie, - pairs.Select(pair => - new KeyValuePair( - StateStoreExtensions.EncodeKey(pair.Key), - pair.Value - ) - ) - ); - - private ITrie Set(ITrie trie, IEnumerable> pairs) - { - foreach (var pair in pairs) - { - if (pair.Value is { } v) - { - trie = trie.Set(pair.Key, v); - } - else - { - throw new NotSupportedException( - "Unsetting states is not supported yet. " + - "See also: https://github.com/planetarium/libplanet/issues/1383" - ); - } - } - - return trie; - } } private sealed class Random : System.Random, IRandom @@ -568,8 +637,7 @@ public Random(int seed) /// Almost duplicate https://github.com/planetarium/libplanet/blob/main/Libplanet/Action/ActionEvaluator.cs#L286. /// private static IEnumerable EvaluateActions( - BlockHash? genesisHash, - ImmutableArray preEvaluationHash, + HashDigest preEvaluationHash, long blockIndex, int blockProtocolVersion, TxId? txid, @@ -578,25 +646,20 @@ private static IEnumerable EvaluateActions( Address signer, byte[] signature, IImmutableList actions, - bool rehearsal = false, - ITrie? previousBlockStatesTrie = null, - bool blockAction = false, ILogger? logger = null) { - ActionContext CreateActionContext(IAccountStateDelta prevStates, int randomSeed) + ActionContext CreateActionContext( + IAccountStateDelta prevStates, + int randomSeed) { return new ActionContext( - genesisHash: genesisHash, signer: signer, txid: txid, miner: miner, blockIndex: blockIndex, blockProtocolVersion: blockProtocolVersion, previousStates: prevStates, - randomSeed: randomSeed, - rehearsal: rehearsal, - previousBlockStatesTrie: previousBlockStatesTrie, - blockAction: blockAction); + randomSeed: randomSeed); } byte[] hashedSignature; @@ -605,25 +668,26 @@ ActionContext CreateActionContext(IAccountStateDelta prevStates, int randomSeed) hashedSignature = hasher.ComputeHash(signature); } - byte[] preEvaluationHashBytes = preEvaluationHash.ToBuilder().ToArray(); - int seed = ActionEvaluator.GenerateRandomSeed( - preEvaluationHashBytes, - hashedSignature, - signature, - 0); + byte[] preEvaluationHashBytes = preEvaluationHash.ToByteArray(); + int seed = ActionEvaluator.GenerateRandomSeed(preEvaluationHashBytes, hashedSignature, signature, 0); IAccountStateDelta states = previousStates; foreach (IAction action in actions) { Exception? exc = null; - ActionContext context = CreateActionContext(states, seed); - IAccountStateDelta nextStates = context.PreviousStates; + IAccountStateDelta nextStates = states; + ActionContext context = CreateActionContext(nextStates, seed); + try { - DateTimeOffset actionExecutionStarted = DateTimeOffset.Now; + Stopwatch stopwatch = new Stopwatch(); + stopwatch.Start(); nextStates = action.Execute(context); - TimeSpan spent = DateTimeOffset.Now - actionExecutionStarted; - logger?.Verbose($"{action} execution spent {spent.TotalMilliseconds} ms."); + logger? + .Information( + "Action {Action} took {DurationMs} ms to execute", + action, + stopwatch.ElapsedMilliseconds); } catch (OutOfMemoryException e) { @@ -632,65 +696,44 @@ ActionContext CreateActionContext(IAccountStateDelta prevStates, int randomSeed) var message = "Action {Action} of tx {TxId} of block #{BlockIndex} with " + "pre-evaluation hash {PreEvaluationHash} threw an exception " + - "during execution."; + "during execution"; logger?.Error( e, message, action, txid, blockIndex, - ByteUtil.Hex(preEvaluationHash)); + ByteUtil.Hex(preEvaluationHash.ByteArray)); throw; } catch (Exception e) { - if (rehearsal) - { - var message = - $"The action {action} threw an exception during its " + - "rehearsal. It is probably because the logic of the " + - $"action {action} is not enough generic so that it " + - "can cover every case including rehearsal mode.\n" + - "The IActionContext.Rehearsal property also might be " + - "useful to make the action can deal with the case of " + - "rehearsal mode.\n" + - "See also this exception's InnerException property."; - exc = new UnexpectedlyTerminatedActionException( - message, null, null, null, null, action, e); - } - else - { - var stateRootHash = context.PreviousStateRootHash; - var message = - "Action {Action} of tx {TxId} of block #{BlockIndex} with " + - "pre-evaluation hash {PreEvaluationHash} and previous " + - "state root hash {StateRootHash} threw an exception " + - "during execution."; - logger?.Error( - e, - message, - action, - txid, - blockIndex, - ByteUtil.Hex(preEvaluationHash), - stateRootHash); - var innerMessage = - $"The action {action} (block #{blockIndex}, " + - $"pre-evaluation hash {ByteUtil.Hex(preEvaluationHash)}, tx {txid}, " + - $"previous state root hash {stateRootHash}) threw " + - "an exception during execution. " + - "See also this exception's InnerException property."; - logger?.Error( - "{Message}\nInnerException: {ExcMessage}", innerMessage, e.Message); - exc = new UnexpectedlyTerminatedActionException( - innerMessage, - new HashDigest(preEvaluationHash), - blockIndex, - txid, - stateRootHash, - action, - e); - } + var message = + "Action {Action} of tx {TxId} of block #{BlockIndex} with " + + "pre-evaluation hash {PreEvaluationHash} threw an exception " + + "during execution"; + logger?.Error( + e, + message, + action, + txid, + blockIndex, + ByteUtil.Hex(preEvaluationHash.ByteArray)); + var innerMessage = + $"The action {action} (block #{blockIndex}, " + + $"pre-evaluation hash {ByteUtil.Hex(preEvaluationHash.ByteArray)}, " + + $"tx {txid} threw an exception during execution. " + + "See also this exception's InnerException property"; + logger?.Error( + "{Message}\nInnerException: {ExcMessage}", innerMessage, e.Message); + exc = new UnexpectedlyTerminatedActionException( + innerMessage, + preEvaluationHash, + blockIndex, + txid, + null, + action, + e); } // As IActionContext.Random is stateful, we cannot reuse diff --git a/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs b/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs index 30af245f1..14d0d278b 100644 --- a/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs @@ -91,23 +91,11 @@ public int Tx( } // Evaluate tx. - IAccountStateDelta previousStates = new AccountStateDeltaImpl( - addresses => blockChain.GetStates( - addresses, - previousBlock.Hash), - (address, currency) => blockChain.GetBalance( - address, - currency, - previousBlock.Hash), - currency => blockChain.GetTotalSupply( - currency, - previousBlock.Hash), - () => blockChain.GetValidatorSet( - previousBlock.Hash)); + IAccountState previousBlockStates = blockChain.GetBlockStates(previousBlock.Hash); + IAccountStateDelta previousStates = AccountStateDelta.Create(previousBlockStates); var actions = tx.Actions.Select(a => ToAction(a)); var actionEvaluations = EvaluateActions( - genesisHash: blockChain.Genesis.Hash, - preEvaluationHash: targetBlock.PreEvaluationHash.ByteArray, + preEvaluationHash: targetBlock.PreEvaluationHash, blockIndex: targetBlock.Index, blockProtocolVersion: targetBlock.ProtocolVersion, txid: tx.Id, @@ -115,9 +103,7 @@ public int Tx( miner: targetBlock.Miner, signer: tx.Signer, signature: tx.Signature, - actions: actions.Cast().ToImmutableList(), - rehearsal: false, - previousBlockStatesTrie: null + actions: actions.Cast().ToImmutableList() ); var actionNum = 1; foreach (var actionEvaluation in actionEvaluations) @@ -152,11 +138,10 @@ public int Tx( var states = actionEvaluation.OutputStates; var addressNum = 1; - foreach (var updatedAddress in states.UpdatedAddresses) + foreach (var (updatedAddress, updatedState) in states.Delta.States) { if (verbose) { - var updatedState = states.GetState(updatedAddress); msg = $"- action #{actionNum} updated address #{addressNum}({updatedAddress}) beginning.."; _console.Out.WriteLine(msg); outputSw?.WriteLine(msg); diff --git a/NineChronicles.Headless.Executable/Commands/StateCommand.cs b/NineChronicles.Headless.Executable/Commands/StateCommand.cs index 9fb92ec7f..4b239c49a 100644 --- a/NineChronicles.Headless.Executable/Commands/StateCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/StateCommand.cs @@ -447,14 +447,13 @@ private static ImmutableDictionary GetTotalDelta( string validatorSetKey) { IImmutableSet
stateUpdatedAddresses = actionEvaluations - .SelectMany(a => a.OutputStates.StateUpdatedAddresses) + .SelectMany(a => a.OutputStates.Delta.StateUpdatedAddresses) .ToImmutableHashSet(); IImmutableSet<(Address, Currency)> updatedFungibleAssets = actionEvaluations - .SelectMany(a => a.OutputStates.UpdatedFungibleAssets - .SelectMany(kv => kv.Value.Select(c => (kv.Key, c)))) + .SelectMany(a => a.OutputStates.Delta.UpdatedFungibleAssets) .ToImmutableHashSet(); IImmutableSet updatedTotalSupplies = actionEvaluations - .SelectMany(a => a.OutputStates.TotalSupplyUpdatedCurrencies) + .SelectMany(a => a.OutputStates.Delta.UpdatedTotalSupplyCurrencies) .ToImmutableHashSet(); if (actionEvaluations.Count == 0) diff --git a/NineChronicles.Headless/ActionEvaluationPublisher.cs b/NineChronicles.Headless/ActionEvaluationPublisher.cs index 814067eb1..e71c41280 100644 --- a/NineChronicles.Headless/ActionEvaluationPublisher.cs +++ b/NineChronicles.Headless/ActionEvaluationPublisher.cs @@ -510,15 +510,13 @@ private bool ContainsAddressToBroadcast(ActionEvaluation ev) private bool ContainsAddressToBroadcastLocal(ActionEvaluation ev) { - var updatedAddresses = - ev.OutputStates.UpdatedAddresses.Union(ev.OutputStates.UpdatedFungibleAssets.Keys); + var updatedAddresses = ev.OutputStates.Delta.UpdatedAddresses; return _context.AddressesToSubscribe.Any(updatedAddresses.Add(ev.Signer).Contains); } private bool ContainsAddressToBroadcastRemoteClient(ActionEvaluation ev) { - var updatedAddresses = - ev.OutputStates.UpdatedAddresses.Union(ev.OutputStates.UpdatedFungibleAssets.Keys); + var updatedAddresses = ev.OutputStates.Delta.UpdatedAddresses; return TargetAddresses.Any(updatedAddresses.Add(ev.Signer).Contains); } } diff --git a/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs b/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs index c972dce7f..c32e5318a 100644 --- a/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs +++ b/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using GraphQL; using GraphQL.Types; +using Bencodex.Types; using Lib9c; using Libplanet.Blockchain; using Libplanet.Tx; @@ -168,10 +170,30 @@ public TransactionHeadlessQuery(StandaloneContext standaloneContext) Block txExecutedBlock = blockChain[txExecutedBlockHash]; return execution switch { - TxSuccess txSuccess => new TxResult(TxStatus.SUCCESS, txExecutedBlock.Index, - txExecutedBlock.Hash.ToString(), null, null, txSuccess.UpdatedStates, txSuccess.FungibleAssetsDelta, txSuccess.UpdatedFungibleAssets, txSuccess.ActionsLogsList), - TxFailure txFailure => new TxResult(TxStatus.FAILURE, txExecutedBlock.Index, - txExecutedBlock.Hash.ToString(), txFailure.ExceptionName, txFailure.ExceptionMetadata, null, null, null, null), + TxSuccess txSuccess => new TxResult( + TxStatus.SUCCESS, + txExecutedBlock.Index, + txExecutedBlock.Hash.ToString(), + null, + null, + txSuccess.UpdatedStates + .Select(kv => new KeyValuePair( + kv.Key, + kv.Value)) + .ToImmutableDictionary(), + txSuccess.FungibleAssetsDelta, + txSuccess.UpdatedFungibleAssets, + txSuccess.ActionsLogsList), + TxFailure txFailure => new TxResult( + TxStatus.FAILURE, + txExecutedBlock.Index, + txExecutedBlock.Hash.ToString(), + txFailure.ExceptionName, + txFailure.ExceptionMetadata, + null, + null, + null, + null), _ => throw new NotImplementedException( $"{nameof(execution)} is not expected concrete class.") }; From 0b351634697de9ab337e2a31ef0e78b05876735c Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Mon, 3 Jul 2023 18:35:30 +0900 Subject: [PATCH 48/89] Introduce `SimplifyCurrencyInputType` --- .../GraphTypes/SimplifyCurrencyInputType.cs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 NineChronicles.Headless/GraphTypes/SimplifyCurrencyInputType.cs diff --git a/NineChronicles.Headless/GraphTypes/SimplifyCurrencyInputType.cs b/NineChronicles.Headless/GraphTypes/SimplifyCurrencyInputType.cs new file mode 100644 index 000000000..7f78508f5 --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/SimplifyCurrencyInputType.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using GraphQL; +using GraphQL.Types; + +namespace NineChronicles.Headless.GraphTypes; + +public class SimplifyCurrencyInputType : InputObjectGraphType +{ + public static void SetFields(ComplexGraphType graphType) + { + graphType.Field( + name: "currencyEnum", + description: "A currency type to be loaded."); + + graphType.Field( + name: "currencyTicker", + description: "A currency ticker to be loaded."); + } + + public static string Parse(IDictionary value) + { + if (value.TryGetValue("currencyEnum", out var currencyEnum)) + { + if (value.ContainsKey("currencyTicker")) + { + throw new ExecutionError("currencyEnum and currencyTicker cannot be specified at the same time."); + } + + return ((CurrencyEnum)currencyEnum!).ToString(); + } + + if (value.TryGetValue("currencyTicker", out var currencyTicker)) + { + return (string)currencyTicker!; + } + + throw new ExecutionError("currencyEnum or currencyTicker must be specified."); + } + + public SimplifyCurrencyInputType() + { + Name = "SimplifyCurrencyInput"; + Description = "A currency ticker to be loaded. Use either currencyEnum or currencyTicker."; + SetFields(this); + } + + public override object ParseDictionary(IDictionary value) + { + return Parse(value); + } +} From ad35c85d273004a54eff7d4e8f7dbc15f5d01209 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Mon, 3 Jul 2023 18:36:08 +0900 Subject: [PATCH 49/89] Apply `SimplifyCurrencyInputType` into `SimplifyFungibleAssetValueInputType` --- .../SimplifyFungibleAssetValueInputType.cs | 64 ++++++------------- 1 file changed, 20 insertions(+), 44 deletions(-) diff --git a/NineChronicles.Headless/GraphTypes/SimplifyFungibleAssetValueInputType.cs b/NineChronicles.Headless/GraphTypes/SimplifyFungibleAssetValueInputType.cs index d30df9768..668fa6d03 100644 --- a/NineChronicles.Headless/GraphTypes/SimplifyFungibleAssetValueInputType.cs +++ b/NineChronicles.Headless/GraphTypes/SimplifyFungibleAssetValueInputType.cs @@ -2,53 +2,29 @@ using GraphQL; using GraphQL.Types; -namespace NineChronicles.Headless.GraphTypes +namespace NineChronicles.Headless.GraphTypes; + +public class SimplifyFungibleAssetValueInputType : + InputObjectGraphType<(string currencyTicker, string value)> { - public class SimplifyFungibleAssetValueInputType : - InputObjectGraphType<(string currencyTicker, string value)> + public SimplifyFungibleAssetValueInputType() { - public SimplifyFungibleAssetValueInputType() - { - Name = "SimplifyFungibleAssetValueInput"; - Description = "A fungible asset value ticker and amount." + - "You can specify either currencyEnum or currencyTicker."; - - Field( - name: "currencyEnum", - description: "A currency type to be loaded."); - - Field( - name: "currencyTicker", - description: "A currency ticker to be loaded."); + Name = "SimplifyFungibleAssetValueInput"; + Description = "A fungible asset value ticker and amount." + + "You can specify either currencyEnum or currencyTicker."; - Field>( - name: "value", - description: "A numeric string to parse. Can consist of digits, " + - "plus (+), minus (-), and decimal separator (.)." + - " "); - } - - public override object ParseDictionary(IDictionary value) - { - var value2 = (string)value["value"]!; - if (value.TryGetValue("currencyEnum", out var currencyEnum)) - { - if (value.ContainsKey("currencyTicker")) - { - throw new ExecutionError("currencyEnum and currencyTicker cannot be specified at the same time."); - } + SimplifyCurrencyInputType.SetFields(this); + Field>( + name: "value", + description: "A numeric string to parse. Can consist of digits, " + + "plus (+), minus (-), and decimal separator (.)." + + " "); + } - var currencyTicker = ((CurrencyEnum)currencyEnum!).ToString(); - return (currencyTicker, value: value2); - } - else if (value.TryGetValue("currencyTicker", out var currencyTicker)) - { - return ((string)currencyTicker!, value: value2); - } - else - { - throw new ExecutionError("currencyEnum or currencyTicker must be specified."); - } - } + public override object ParseDictionary(IDictionary value) + { + var value2 = (string)value["value"]!; + var currencyTicker = SimplifyCurrencyInputType.Parse(value); + return (currencyTicker, value: value2); } } From 31f66c439172ab7ffe3e00143e3728d2f0d60cfb Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Tue, 4 Jul 2023 14:00:37 +0900 Subject: [PATCH 50/89] Introduce `CurrencyFactory` util --- .../Utils/CurrencyFactory.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 NineChronicles.Headless/Utils/CurrencyFactory.cs diff --git a/NineChronicles.Headless/Utils/CurrencyFactory.cs b/NineChronicles.Headless/Utils/CurrencyFactory.cs new file mode 100644 index 000000000..360caab7a --- /dev/null +++ b/NineChronicles.Headless/Utils/CurrencyFactory.cs @@ -0,0 +1,62 @@ +using Bencodex.Types; +using Lib9c; +using Libplanet.Assets; +using Libplanet.State; +using Nekoyume; +using Nekoyume.Model.State; +using NineChronicles.Headless.GraphTypes; + +namespace NineChronicles.Headless.Utils; + +public class CurrencyFactory +{ + private readonly AccountStateGetter _accountStateGetter; + private Currency? _ncg; + + public CurrencyFactory( + AccountStateGetter accountStateGetter, + Currency? ncg = null) + { + _accountStateGetter = accountStateGetter; + _ncg = ncg; + } + + public bool TryGetCurrency(CurrencyEnum currencyEnum, out Currency currency) + { + return TryGetCurrency(currencyEnum.ToString(), out currency); + } + + public bool TryGetCurrency(string ticker, out Currency currency) + { + var result = ticker switch + { + "NCG" => GetNCG(), + _ => Currencies.GetMinterlessCurrency(ticker), + }; + if (result is null) + { + currency = default; + return false; + } + + currency = result.Value; + return true; + } + + private Currency? GetNCG() + { + if (_ncg is not null) + { + return _ncg; + } + + var value = _accountStateGetter(new[] { Addresses.GoldCurrency })[0]; + if (value is Dictionary goldCurrencyDict) + { + var goldCurrency = new GoldCurrencyState(goldCurrencyDict); + _ncg = goldCurrency.Currency; + } + + return _ncg; + } +} From 0848c2e7b6918e890749e5d49df614fcd7b6f57b Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Tue, 4 Jul 2023 14:00:50 +0900 Subject: [PATCH 51/89] Introduce `FungibleAssetValueFactory` util --- .../Utils/FungibleAssetValueFactory.cs | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 NineChronicles.Headless/Utils/FungibleAssetValueFactory.cs diff --git a/NineChronicles.Headless/Utils/FungibleAssetValueFactory.cs b/NineChronicles.Headless/Utils/FungibleAssetValueFactory.cs new file mode 100644 index 000000000..e43f45483 --- /dev/null +++ b/NineChronicles.Headless/Utils/FungibleAssetValueFactory.cs @@ -0,0 +1,70 @@ +using System.Numerics; +using Libplanet.Assets; +using NineChronicles.Headless.GraphTypes; + +namespace NineChronicles.Headless.Utils; + +public class FungibleAssetValueFactory +{ + private readonly CurrencyFactory _currencyFactory; + + public FungibleAssetValueFactory(CurrencyFactory currencyFactory) + { + _currencyFactory = currencyFactory; + } + + public bool TryGetFungibleAssetValue( + CurrencyEnum currencyEnum, + BigInteger majorUnit, + BigInteger minorUnit, + out FungibleAssetValue fungibleAssetValue) + { + return TryGetFungibleAssetValue( + currencyEnum.ToString(), + majorUnit, + minorUnit, + out fungibleAssetValue); + } + + public bool TryGetFungibleAssetValue( + string ticker, + BigInteger majorUnit, + BigInteger minorUnit, + out FungibleAssetValue fungibleAssetValue) + { + if (!_currencyFactory.TryGetCurrency(ticker, out var currency)) + { + fungibleAssetValue = default; + return false; + } + + fungibleAssetValue = new FungibleAssetValue(currency, majorUnit, minorUnit); + return true; + } + + public bool TryGetFungibleAssetValue( + CurrencyEnum currencyEnum, + string value, + out FungibleAssetValue fungibleAssetValue) + { + return TryGetFungibleAssetValue( + currencyEnum.ToString(), + value, + out fungibleAssetValue); + } + + public bool TryGetFungibleAssetValue( + string ticker, + string value, + out FungibleAssetValue fungibleAssetValue) + { + if (!_currencyFactory.TryGetCurrency(ticker, out var currency)) + { + fungibleAssetValue = default; + return false; + } + + fungibleAssetValue = FungibleAssetValue.Parse(currency, value); + return true; + } +} From 1d8a891b3d3bb79d84fc8586014d6a074cc0f478 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Tue, 4 Jul 2023 14:01:53 +0900 Subject: [PATCH 52/89] Refactor `StandaloneContext` --- .../GraphQLTestUtils.cs | 15 ++- .../TransactionHeadlessQueryTest.cs | 7 +- .../GraphTypes/ActionQuery.cs | 2 +- NineChronicles.Headless/StandaloneContext.cs | 102 ++---------------- 4 files changed, 29 insertions(+), 97 deletions(-) diff --git a/NineChronicles.Headless.Tests/GraphQLTestUtils.cs b/NineChronicles.Headless.Tests/GraphQLTestUtils.cs index 8c5c6fab5..f3c30f78a 100644 --- a/NineChronicles.Headless.Tests/GraphQLTestUtils.cs +++ b/NineChronicles.Headless.Tests/GraphQLTestUtils.cs @@ -18,6 +18,7 @@ using Nekoyume.Action; using Nekoyume.Action.Loader; using Nekoyume.Model.State; +using NineChronicles.Headless.Utils; namespace NineChronicles.Headless.Tests { @@ -34,7 +35,7 @@ public static Task ExecuteQueryAsync( { var services = new ServiceCollection(); services.AddSingleton(typeof(TObjectGraphType)); - if (!(standaloneContext is null)) + if (standaloneContext is not null) { services.AddSingleton(standaloneContext); } @@ -71,7 +72,8 @@ public static Task ExecuteQueryAsync( } // FIXME: Passing 0 index is bad. - public static ActionBase DeserializeNCAction(IValue value) => (ActionBase)_actionLoader.LoadAction(0, value); + public static ActionBase DeserializeNCAction(IValue value) => + (ActionBase)_actionLoader.LoadAction(0, value); public static StandaloneContext CreateStandaloneContext() { @@ -91,10 +93,14 @@ public static StandaloneContext CreateStandaloneContext() stateStore, genesisBlock, actionEvaluator); + var currencyFactory = new CurrencyFactory(blockchain.GetStates); + var fungibleAssetValueFactory = new FungibleAssetValueFactory(currencyFactory); return new StandaloneContext { BlockChain = blockchain, Store = store, + CurrencyFactory = currencyFactory, + FungibleAssetValueFactory = fungibleAssetValueFactory, }; } @@ -129,11 +135,14 @@ PrivateKey minerPrivateKey actionEvaluator); var ncg = new GoldCurrencyState((Dictionary)blockchain.GetState(Addresses.GoldCurrency)) .Currency; + var currencyFactory = new CurrencyFactory(blockchain.GetStates, ncg); + var fungibleAssetValueFactory = new FungibleAssetValueFactory(currencyFactory); return new StandaloneContext { BlockChain = blockchain, Store = store, - NCG = ncg, + CurrencyFactory = currencyFactory, + FungibleAssetValueFactory = fungibleAssetValueFactory, }; } } diff --git a/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs index 835dec679..b4f9b87c1 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs @@ -25,6 +25,7 @@ using Nekoyume.Action.Loader; using NineChronicles.Headless.GraphTypes; using NineChronicles.Headless.Tests.Common; +using NineChronicles.Headless.Utils; using Xunit; using static NineChronicles.Headless.NCActionUtils; @@ -343,11 +344,15 @@ public async Task TransactionResultIsSuccess() private Task ExecuteAsync(string query) { + var currencyFactory = new CurrencyFactory(_blockChain.GetStates); + var fungibleAssetValueFactory = new FungibleAssetValueFactory(currencyFactory); return GraphQLTestUtils.ExecuteQueryAsync(query, standaloneContext: new StandaloneContext { BlockChain = _blockChain, Store = _store, - NineChroniclesNodeService = _service + NineChroniclesNodeService = _service, + CurrencyFactory = currencyFactory, + FungibleAssetValueFactory = fungibleAssetValueFactory, }); } diff --git a/NineChronicles.Headless/GraphTypes/ActionQuery.cs b/NineChronicles.Headless/GraphTypes/ActionQuery.cs index a41ef6557..3ca309ced 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQuery.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQuery.cs @@ -188,7 +188,7 @@ public ActionQuery(StandaloneContext standaloneContext) var sender = context.GetArgument
("sender"); var recipient = context.GetArgument
("recipient"); var currencyEnum = context.GetArgument("currency"); - if (!standaloneContext.TryGetCurrency(currencyEnum, out var currency)) + if (!standaloneContext.CurrencyFactory?.TryGetCurrency(currencyEnum, out var currency) ?? true) { throw new ExecutionError($"Currency {currencyEnum} is not found."); } diff --git a/NineChronicles.Headless/StandaloneContext.cs b/NineChronicles.Headless/StandaloneContext.cs index f43a037a9..0ded0c3bb 100644 --- a/NineChronicles.Headless/StandaloneContext.cs +++ b/NineChronicles.Headless/StandaloneContext.cs @@ -14,6 +14,7 @@ using Nekoyume; using Nekoyume.Model.State; using NineChronicles.Headless.GraphTypes; +using NineChronicles.Headless.Utils; namespace NineChronicles.Headless { @@ -26,17 +27,19 @@ public class StandaloneContext public bool IsMining { get; set; } public ReplaySubject NodeStatusSubject { get; } = new ReplaySubject(1); public ReplaySubject PreloadStateSubject { get; } = new ReplaySubject(5); + public Subject DifferentAppProtocolVersionEncounterSubject { get; } = new Subject(); + public Subject NotificationSubject { get; } = new Subject(); public Subject NodeExceptionSubject { get; } = new Subject(); public NineChroniclesNodeService? NineChroniclesNodeService { get; set; } public ConcurrentDictionary statusSubject, ReplaySubject stateSubject, ReplaySubject balanceSubject)> - AgentAddresses - { get; } = new ConcurrentDictionary, ReplaySubject, ReplaySubject)>(); + (ReplaySubject statusSubject, ReplaySubject + stateSubject, ReplaySubject balanceSubject)> + AgentAddresses { get; } = new ConcurrentDictionary, ReplaySubject, ReplaySubject)>(); public NodeStatusType NodeStatus => new NodeStatusType(this) { @@ -49,7 +52,9 @@ public class StandaloneContext public Swarm? Swarm { get; internal set; } - public Currency? NCG { get; internal set; } + public CurrencyFactory? CurrencyFactory { get; set; } + + public FungibleAssetValueFactory? FungibleAssetValueFactory { get; set; } internal TimeSpan DifferentAppProtocolVersionEncounterInterval { get; set; } = TimeSpan.FromSeconds(30); @@ -60,92 +65,5 @@ public class StandaloneContext internal TimeSpan MonsterCollectionStateInterval { get; set; } = TimeSpan.FromSeconds(30); internal TimeSpan MonsterCollectionStatusInterval { get; set; } = TimeSpan.FromSeconds(30); - - public bool TryGetCurrency(CurrencyEnum currencyEnum, out Currency? currency) - { - return TryGetCurrency(currencyEnum.ToString(), out currency); - } - - public bool TryGetCurrency(string ticker, out Currency? currency) - { - currency = ticker switch - { - "NCG" => GetNCG(), - _ => Currencies.GetMinterlessCurrency(ticker), - }; - return currency is not null; - } - - private Currency? GetNCG() - { - if (NCG is not null) - { - return NCG; - } - - if (BlockChain?.GetState(Addresses.GoldCurrency) is - Dictionary goldCurrencyDict) - { - var goldCurrency = new GoldCurrencyState(goldCurrencyDict); - NCG = goldCurrency.Currency; - } - - return NCG; - } - - public bool TryGetFungibleAssetValue( - CurrencyEnum currencyEnum, - BigInteger majorUnit, - BigInteger minorUnit, - out FungibleAssetValue? fungibleAssetValue) - { - return TryGetFungibleAssetValue( - currencyEnum.ToString(), - majorUnit, - minorUnit, - out fungibleAssetValue); - } - - public bool TryGetFungibleAssetValue( - string ticker, - BigInteger majorUnit, - BigInteger minorUnit, - out FungibleAssetValue? fungibleAssetValue) - { - if (TryGetCurrency(ticker, out var currency)) - { - fungibleAssetValue = new FungibleAssetValue(currency!.Value, majorUnit, minorUnit); - return true; - } - - fungibleAssetValue = null; - return false; - } - - public bool TryGetFungibleAssetValue( - CurrencyEnum currencyEnum, - string value, - out FungibleAssetValue? fungibleAssetValue) - { - return TryGetFungibleAssetValue( - currencyEnum.ToString(), - value, - out fungibleAssetValue); - } - - public bool TryGetFungibleAssetValue( - string ticker, - string value, - out FungibleAssetValue? fungibleAssetValue) - { - if (TryGetCurrency(ticker, out var currency)) - { - fungibleAssetValue = FungibleAssetValue.Parse(currency!.Value, value); - return true; - } - - fungibleAssetValue = null; - return false; - } } } From 383b1cad7d08df932708188b97b4534615e35a95 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Tue, 4 Jul 2023 14:02:34 +0900 Subject: [PATCH 53/89] Expand `StateContext` --- .../GraphTypes/States/StateContext.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/NineChronicles.Headless/GraphTypes/States/StateContext.cs b/NineChronicles.Headless/GraphTypes/States/StateContext.cs index 8e1a55443..3f544c3da 100644 --- a/NineChronicles.Headless/GraphTypes/States/StateContext.cs +++ b/NineChronicles.Headless/GraphTypes/States/StateContext.cs @@ -1,25 +1,36 @@ +#nullable enable + using System.Collections.Generic; using Bencodex.Types; using Libplanet; -using Libplanet.Action; using Libplanet.Assets; using Libplanet.State; +using NineChronicles.Headless.Utils; namespace NineChronicles.Headless.GraphTypes.States { public class StateContext { - public StateContext(AccountStateGetter accountStateGetter, AccountBalanceGetter accountBalanceGetter, long blockIndex) + public StateContext( + AccountStateGetter accountStateGetter, + AccountBalanceGetter accountBalanceGetter, + long blockIndex) { AccountStateGetter = accountStateGetter; AccountBalanceGetter = accountBalanceGetter; BlockIndex = blockIndex; + CurrencyFactory = new CurrencyFactory(accountStateGetter); + FungibleAssetValueFactory = new FungibleAssetValueFactory(CurrencyFactory); } public AccountStateGetter AccountStateGetter { get; } public AccountBalanceGetter AccountBalanceGetter { get; } public long BlockIndex { get; } + public CurrencyFactory CurrencyFactory { get; } + + public FungibleAssetValueFactory FungibleAssetValueFactory { get; } + public IValue? GetState(Address address) => AccountStateGetter(new[] { address })[0]; From 090999ef107e4eb8a5328b51fc9c714bfc4956e4 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Tue, 4 Jul 2023 14:56:17 +0900 Subject: [PATCH 54/89] Introduce `ItemType` --- .../GraphTypes/States/Models/Item/ItemType.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 NineChronicles.Headless/GraphTypes/States/Models/Item/ItemType.cs diff --git a/NineChronicles.Headless/GraphTypes/States/Models/Item/ItemType.cs b/NineChronicles.Headless/GraphTypes/States/Models/Item/ItemType.cs new file mode 100644 index 000000000..80b4f0aa3 --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/States/Models/Item/ItemType.cs @@ -0,0 +1,22 @@ +using GraphQL.Types; +using Nekoyume.Model.Item; +using NineChronicles.Headless.GraphTypes.States.Models.Item.Enum; + +namespace NineChronicles.Headless.GraphTypes.States.Models.Item; + +public class ItemType : ObjectGraphType where T : IItem +{ + protected ItemType() + { + Field>( + "itemType", + description: "Item category.", + resolve: context => context.Source.ItemType + ); + Field>( + "itemSubType", + description: "Item sub category.", + resolve: context => context.Source.ItemSubType + ); + } +} From a050a004418e6c9f018ce4761acc111aa835ee20 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Tue, 4 Jul 2023 14:56:26 +0900 Subject: [PATCH 55/89] Introduce `FungibleItemType` --- .../States/Models/Item/FungibleItemType.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 NineChronicles.Headless/GraphTypes/States/Models/Item/FungibleItemType.cs diff --git a/NineChronicles.Headless/GraphTypes/States/Models/Item/FungibleItemType.cs b/NineChronicles.Headless/GraphTypes/States/Models/Item/FungibleItemType.cs new file mode 100644 index 000000000..1c5805328 --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/States/Models/Item/FungibleItemType.cs @@ -0,0 +1,14 @@ +using GraphQL.Types; +using Nekoyume.Model.Item; + +namespace NineChronicles.Headless.GraphTypes.States.Models.Item; + +public class FungibleItemType : ItemType +{ + public FungibleItemType() + { + Field>( + "fungibleItemId", + resolve: context => context.Source.FungibleId.ToString()); + } +} From 71c5e618cd4cd3b83c7b0cff1834a8d0ef4e6143 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Tue, 4 Jul 2023 14:56:45 +0900 Subject: [PATCH 56/89] Fix `FungibleItemGarageType` --- .../GraphTypes/States/Models/Garage/FungibleItemGarageType.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NineChronicles.Headless/GraphTypes/States/Models/Garage/FungibleItemGarageType.cs b/NineChronicles.Headless/GraphTypes/States/Models/Garage/FungibleItemGarageType.cs index a1f226ecd..6daa6158d 100644 --- a/NineChronicles.Headless/GraphTypes/States/Models/Garage/FungibleItemGarageType.cs +++ b/NineChronicles.Headless/GraphTypes/States/Models/Garage/FungibleItemGarageType.cs @@ -1,5 +1,6 @@ using GraphQL.Types; using Nekoyume.Model.Garages; +using NineChronicles.Headless.GraphTypes.States.Models.Item; namespace NineChronicles.Headless.GraphTypes.States.Models.Garage; @@ -7,7 +8,7 @@ public class FungibleItemGarageType : ObjectGraphType { public FungibleItemGarageType() { - Field(name: "fungibleItemId", resolve: context => context.Source.Item.FungibleId); + Field(name: "item", resolve: context => context.Source.Item); Field(name: "count", resolve: context => context.Source.Count); } } From 0fa8c135d34803288fe5157fd54933c7974798c6 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Tue, 4 Jul 2023 14:57:00 +0900 Subject: [PATCH 57/89] Introduce `WithAddressType` --- .../GraphTypes/WithAddressType.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 NineChronicles.Headless/GraphTypes/WithAddressType.cs diff --git a/NineChronicles.Headless/GraphTypes/WithAddressType.cs b/NineChronicles.Headless/GraphTypes/WithAddressType.cs new file mode 100644 index 000000000..04219de60 --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/WithAddressType.cs @@ -0,0 +1,23 @@ +using GraphQL.Types; +using Libplanet; +using Libplanet.Explorer.GraphTypes; + +namespace NineChronicles.Headless.GraphTypes; + +public sealed class WithAddressType : + ObjectGraphType<(TSourceType source, Address addr)> + where TGraphType : IComplexGraphType, new() +{ + public WithAddressType() + { + var t = new TGraphType(); + foreach (var field in t.Fields) + { + AddField(field); + } + + Field( + "address", + resolve: context => context.Source.addr); + } +} From 4e8c688db9042004412b37fdc5c43ed27d2dcb80 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Tue, 4 Jul 2023 14:57:11 +0900 Subject: [PATCH 58/89] Introduce `FungibleItemIdInputType` --- .../GraphTypes/FungibleItemIdInputType.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 NineChronicles.Headless/GraphTypes/FungibleItemIdInputType.cs diff --git a/NineChronicles.Headless/GraphTypes/FungibleItemIdInputType.cs b/NineChronicles.Headless/GraphTypes/FungibleItemIdInputType.cs new file mode 100644 index 000000000..cc2cc97d5 --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/FungibleItemIdInputType.cs @@ -0,0 +1,59 @@ +#nullable enable + +using System.Collections.Generic; +using System.Security.Cryptography; +using GraphQL; +using GraphQL.Types; +using Libplanet; +using Nekoyume.TableData; + +namespace NineChronicles.Headless.GraphTypes; + +public class FungibleItemIdInputType : InputObjectGraphType<(string? fungibleItemId, int? itemSheetId)> +{ + public static void SetFields(ComplexGraphType graphType) + { + graphType.Field( + name: "fungibleItemId", + description: "A fungible item id to be loaded."); + + graphType.Field( + name: "itemSheetId", + description: "(not recommended)A item sheet id to be loaded.\n" + + "It can be does not match with the actual fungible item id " + + "if the item sheets were patched."); + } + + public static (string? fungibleItemId, int? itemSheetId) Parse( + IDictionary value) + { + if (value.TryGetValue("fungibleItemId", out var fungibleItemId)) + { + if (value.TryGetValue("itemSheetId", out _)) + { + throw new ExecutionError("fungibleItemId and itemSheetId cannot be specified at the same time."); + } + + return ((string)fungibleItemId!, null); + } + + if (value.TryGetValue("itemSheetId", out var itemSheetId)) + { + return (null, int.Parse((string)itemSheetId!)); + } + + throw new ExecutionError("fungibleItemId or itemSheetId must be specified."); + } + + public FungibleItemIdInputType() + { + Name = "FungibleItemIdInput"; + Description = "A fungible item id to be loaded. Use either fungibleItemId or itemSheetId."; + SetFields(this); + } + + public override object ParseDictionary(IDictionary value) + { + return Parse(value); + } +} From 53da5dcceec75574b165187ecfa3c0499a59c63b Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Tue, 4 Jul 2023 14:57:35 +0900 Subject: [PATCH 59/89] Refactor `GarageStateType` to `GaragesType` --- .../GraphTypes/States/GarageStateType.cs | 22 --------- .../GraphTypes/States/GaragesType.cs | 49 +++++++++++++++++++ 2 files changed, 49 insertions(+), 22 deletions(-) delete mode 100644 NineChronicles.Headless/GraphTypes/States/GarageStateType.cs create mode 100644 NineChronicles.Headless/GraphTypes/States/GaragesType.cs diff --git a/NineChronicles.Headless/GraphTypes/States/GarageStateType.cs b/NineChronicles.Headless/GraphTypes/States/GarageStateType.cs deleted file mode 100644 index d52014708..000000000 --- a/NineChronicles.Headless/GraphTypes/States/GarageStateType.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; -using System.Security.Cryptography; -using Bencodex.Types; -using GraphQL.Types; -using Lib9c; -using Libplanet; -using Libplanet.Assets; -using Libplanet.State; -using Nekoyume.Model.Garages; -using NineChronicles.Headless.GraphTypes.States.Models.Garage; - -namespace NineChronicles.Headless.GraphTypes.States; - -public class GarageStateType : ObjectGraphType<(FungibleAssetValue balance, IReadOnlyList fungibleItemList)> -{ - public GarageStateType() - { - Field(name: "balance", resolve: context => context.Source.balance); - Field>(name: "fungibleItemList", - resolve: context => context.Source.fungibleItemList); - } -} diff --git a/NineChronicles.Headless/GraphTypes/States/GaragesType.cs b/NineChronicles.Headless/GraphTypes/States/GaragesType.cs new file mode 100644 index 000000000..15838aee5 --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/States/GaragesType.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Linq; +using GraphQL.Types; +using Libplanet; +using Libplanet.Assets; +using Libplanet.Explorer.GraphTypes; +using Nekoyume.Model.Garages; +using NineChronicles.Headless.GraphTypes.States.Models.Garage; + +namespace NineChronicles.Headless.GraphTypes.States; + +public class GaragesType : ObjectGraphType +{ + public struct Value + { + public readonly Address AgentAddr; + public readonly Address GarageBalancesAddr; + public readonly FungibleAssetValue[] FungibleAssetValues; + public readonly (FungibleItemGarage fungibleItemGarage, Address addr)[] FungibleItemGarages; + + public Value( + Address agentAddr, + Address garageBalancesAddr, + IEnumerable fungibleAssetValues, + IEnumerable<(FungibleItemGarage fungibleItemGarage, Address addr)> fungibleItemGarages) + { + AgentAddr = agentAddr; + GarageBalancesAddr = garageBalancesAddr; + FungibleAssetValues = fungibleAssetValues.ToArray(); + FungibleItemGarages = fungibleItemGarages.ToArray(); + } + } + + public GaragesType() + { + Field( + name: "agentAddr", + resolve: context => context.Source.AgentAddr); + Field( + name: "garageBalancesAddr", + resolve: context => context.Source.GarageBalancesAddr); + Field>( + name: "garageBalances", + resolve: context => context.Source.FungibleAssetValues); + Field>>( + name: "fungibleItemGarages", + resolve: context => context.Source.FungibleItemGarages); + } +} From 234991d46aac67cae35eecc9d4996c248bb37c1c Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Tue, 4 Jul 2023 14:57:54 +0900 Subject: [PATCH 60/89] Fix `Garages` state query --- .../GraphTypes/StateQueryFields/Garages.cs | 113 +++++++++++++----- 1 file changed, 86 insertions(+), 27 deletions(-) diff --git a/NineChronicles.Headless/GraphTypes/StateQueryFields/Garages.cs b/NineChronicles.Headless/GraphTypes/StateQueryFields/Garages.cs index 9557a3b70..c033eefb1 100644 --- a/NineChronicles.Headless/GraphTypes/StateQueryFields/Garages.cs +++ b/NineChronicles.Headless/GraphTypes/StateQueryFields/Garages.cs @@ -1,45 +1,104 @@ using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; +using Bencodex.Types; using GraphQL; using GraphQL.Types; -using Lib9c; using Libplanet; +using Libplanet.Assets; using Libplanet.Explorer.GraphTypes; using Nekoyume; +using Nekoyume.Model.Garages; +using Nekoyume.TableData; using NineChronicles.Headless.GraphTypes.States; -namespace NineChronicles.Headless.GraphTypes +namespace NineChronicles.Headless.GraphTypes; + +public partial class StateQuery { - public partial class StateQuery + private void RegisterGarages() { - private void RegisterGarages() - { - Field( - "garages", - arguments: new QueryArguments( - new QueryArgument> - { - Name = "agentAddr", - Description = "Address to get GARAGE token balance and fungible items" - }, - new QueryArgument>> + Field( + "garages", + arguments: new QueryArguments( + new QueryArgument> + { + Name = "agentAddr", + Description = "Agent address to get balances and fungible items in garages", + }, + new QueryArgument>> + { + Name = "currencyTickers", + Description = "List of currency tickers to get balances in garages", + }, + new QueryArgument>> + { + Name = "fungibleItemIds", + Description = "List of fungible item IDs to get fungible item in garages", + } + ), + resolve: context => + { + var agentAddr = context.GetArgument
("agentAddr"); + var garageBalanceAddr = Addresses.GetGarageBalanceAddress(agentAddr); + var currencyTickers = context.GetArgument("currencyTickers"); + var fungibleAssetValues = new List(); + foreach (var currencyTicker in currencyTickers) + { + if (!context.Source.CurrencyFactory.TryGetCurrency(currencyTicker, out var currency)) { - Name = "fungibleIds", - Description = "List of fungible item IDs to get stock in garage" + throw new ExecutionError($"Invalid currency ticker: {currencyTicker}"); } - ), - resolve: context => + + var balance = context.Source.GetBalance(garageBalanceAddr, currency); + fungibleAssetValues.Add(balance); + } + + var materialItemSheetAddr = Addresses.GetSheetAddress(); + var materialItemSheetValue = context.Source.GetState(materialItemSheetAddr); + if (materialItemSheetValue is null) { - var agentAddr = context.GetArgument
("agentAddr"); - var balance = context.Source.GetBalance(agentAddr, Currencies.Garage); - var fungibleItemIdList = context.GetArgument>("fungibleIds"); - IEnumerable
fungibleItemAddressList = fungibleItemIdList.Select(fungibleItemId => - Addresses.GetGarageAddress(agentAddr, HashDigest.FromString(fungibleItemId))); - var fungibleItemList = context.Source.GetStates(fungibleItemAddressList.ToArray()); - return (balance, fungibleItemList); + throw new ExecutionError($"{nameof(MaterialItemSheet)} not found: {materialItemSheetAddr}"); } - ); - } + + var materialItemSheet = new MaterialItemSheet(); + materialItemSheet.Set((Text)materialItemSheetValue); + var fungibleItemIdTuples = + context.GetArgument<(string? fungibleItemId, int? itemSheetId)[]>("fungibleItemIds"); + var fungibleItemGarageAddresses = fungibleItemIdTuples + .Select(tuple => + { + var (fungibleItemId, itemSheetId) = tuple; + if (fungibleItemId is not null) + { + return Addresses.GetGarageAddress( + agentAddr, + HashDigest.FromString(fungibleItemId)); + } + + if (itemSheetId is not null) + { + var row = materialItemSheet.OrderedList!.FirstOrDefault(r => r.Id == itemSheetId); + if (row is null) + { + throw new ExecutionError($"Invalid item sheet id: {itemSheetId}"); + } + + return Addresses.GetGarageAddress(agentAddr, row.ItemId); + } + + throw new ExecutionError( + $"Invalid argument: {nameof(fungibleItemId)} or {nameof(itemSheetId)} must be specified."); + }) + .ToArray(); + var fungibleItemGarages = context.Source.GetStates(fungibleItemGarageAddresses) + .Select((value, i) => (new FungibleItemGarage(value), fungibleItemGarageAddresses[i])); + return new GaragesType.Value( + agentAddr, + garageBalanceAddr, + fungibleAssetValues, + fungibleItemGarages); + } + ); } } From f054d49ac10fd06ed44b859f9f781107409591a8 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Tue, 4 Jul 2023 14:58:32 +0900 Subject: [PATCH 61/89] (WIP) Test `garages` state query --- .../GraphTypes/StateQueryTest.cs | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs diff --git a/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs new file mode 100644 index 000000000..690ca94c5 --- /dev/null +++ b/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs @@ -0,0 +1,92 @@ +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using Bencodex; +using Bencodex.Types; +using GraphQL.Execution; +using Libplanet; +using Libplanet.Assets; +using Nekoyume.Action.Garages; +using NineChronicles.Headless.GraphTypes; +using Xunit; +using static NineChronicles.Headless.Tests.GraphQLTestUtils; + +namespace NineChronicles.Headless.Tests.GraphTypes +{ + public class StateQueryTest + { + private readonly Codec _codec; + + public StateQueryTest() + { + _codec = new Codec(); + } + + [Theory] + [MemberData(nameof(GetMemberDataOfLoadIntoMyGarages))] + public async Task Garage( + IEnumerable<(Address balanceAddr, FungibleAssetValue value)>? fungibleAssetValues, + Address? inventoryAddr, + IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts, + string? memo) + { + var expectedAction = new LoadIntoMyGarages( + fungibleAssetValues, + inventoryAddr, + fungibleIdAndCounts, + memo); + var sb = new StringBuilder("{ loadIntoMyGarages("); + if (fungibleAssetValues is not null) + { + sb.Append("fungibleAssetValues: ["); + sb.Append(string.Join(",", fungibleAssetValues.Select(tuple => + $"{{ balanceAddr: \"{tuple.balanceAddr.ToHex()}\", " + + $"value: {{ currencyTicker: \"{tuple.value.Currency.Ticker}\"," + + $"value: \"{tuple.value.GetQuantityString()}\" }} }}"))); + sb.Append("],"); + } + + if (inventoryAddr is not null) + { + sb.Append($"inventoryAddr: \"{inventoryAddr.Value.ToHex()}\","); + } + + if (fungibleIdAndCounts is not null) + { + sb.Append("fungibleIdAndCounts: ["); + sb.Append(string.Join(",", fungibleIdAndCounts.Select(tuple => + $"{{ fungibleId: \"{tuple.fungibleId.ToString()}\", " + + $"count: {tuple.count} }}"))); + sb.Append("],"); + } + + if (memo is not null) + { + sb.Append($"memo: \"{memo}\""); + } + + // Remove last ',' if exists. + if (sb[^1] == ',') + { + sb.Remove(sb.Length - 1, 1); + } + + sb.Append(") }"); + var queryResult = await ExecuteQueryAsync(sb.ToString()); + Assert.Null(queryResult.Errors); + var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; + var plainValue = _codec.Decode(ByteUtil.ParseHex((string)data["loadIntoMyGarages"])); + Assert.IsType(plainValue); + var actionBase = DeserializeNCAction(plainValue); + var actualAction = Assert.IsType(actionBase); + Assert.True(expectedAction.FungibleAssetValues?.SequenceEqual(actualAction.FungibleAssetValues) ?? + actualAction.FungibleAssetValues is null); + Assert.Equal(expectedAction.InventoryAddr, actualAction.InventoryAddr); + Assert.True(expectedAction.FungibleIdAndCounts?.SequenceEqual(actualAction.FungibleIdAndCounts) ?? + actualAction.FungibleIdAndCounts is null); + Assert.Equal(expectedAction.Memo, actualAction.Memo); + } + } +} From 79ce5db7b0140275167c83fec91093cff93f8cfa Mon Sep 17 00:00:00 2001 From: hyeon Date: Tue, 4 Jul 2023 16:32:25 +0900 Subject: [PATCH 62/89] Make project success to build --- .../Commands/ChainCommandTest.cs | 5 ++--- .../Commands/AccountCommand.cs | 10 +++++----- .../Commands/ChainCommand.cs | 5 ++--- .../Commands/GenesisCommand.cs | 6 +++--- .../Commands/MarketCommand.cs | 10 +++++----- .../Commands/StateCommand.cs | 15 ++++++++------- NineChronicles.Headless/GraphTypes/ActionQuery.cs | 5 +++-- .../GraphTypes/ActionQueryFields/Garages.cs | 12 ++++++------ .../GraphTypes/AddressQuery.cs | 4 ++-- .../GraphTypes/StandaloneQuery.cs | 2 +- .../GraphTypes/States/GaragesType.cs | 12 ++++++------ 11 files changed, 43 insertions(+), 43 deletions(-) diff --git a/NineChronicles.Headless.Executable.Tests/Commands/ChainCommandTest.cs b/NineChronicles.Headless.Executable.Tests/Commands/ChainCommandTest.cs index e9ae26180..f3ecc3591 100644 --- a/NineChronicles.Headless.Executable.Tests/Commands/ChainCommandTest.cs +++ b/NineChronicles.Headless.Executable.Tests/Commands/ChainCommandTest.cs @@ -9,12 +9,10 @@ using Bencodex.Types; using Libplanet; using Libplanet.Action; -using Libplanet.Action.Loader; using Libplanet.Action.Sys; using Libplanet.Blockchain; using Libplanet.Blockchain.Policies; using Libplanet.Blocks; -using Libplanet.Extensions.Cocona; using Libplanet.Consensus; using Libplanet.Crypto; using Libplanet.RocksDBStore; @@ -33,6 +31,7 @@ using Serilog.Core; using Xunit; using Lib9cUtils = Lib9c.DevExtensions.Utils; +using CoconaUtils = Libplanet.Extensions.Cocona.Utils; namespace NineChronicles.Headless.Executable.Tests.Commands { @@ -78,7 +77,7 @@ public void Tip(StoreType storeType) _command.Tip(storeType, _storePath); Assert.Equal( - Utils.SerializeHumanReadable(genesisBlock.Header), + CoconaUtils.SerializeHumanReadable(genesisBlock.Header), _console.Out.ToString().Trim() ); } diff --git a/NineChronicles.Headless.Executable/Commands/AccountCommand.cs b/NineChronicles.Headless.Executable/Commands/AccountCommand.cs index 99e912059..3c6ca5f2f 100644 --- a/NineChronicles.Headless.Executable/Commands/AccountCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/AccountCommand.cs @@ -3,7 +3,6 @@ using System.Linq; using Bencodex; using Cocona; -using Lib9c.DevExtensions; using Libplanet; using Libplanet.Assets; using Libplanet.Blockchain; @@ -15,6 +14,7 @@ using NineChronicles.Headless.Executable.IO; using Serilog.Core; using static NineChronicles.Headless.NCActionUtils; +using DevExUtils = Lib9c.DevExtensions.Utils; namespace NineChronicles.Headless.Executable.Commands { @@ -45,11 +45,11 @@ public void Balance( string? address = null ) { - using Logger logger = Utils.ConfigureLogger(verbose); + using Logger logger = DevExUtils.ConfigureLogger(verbose); (BlockChain chain, IStore store, _, _) = - Utils.GetBlockChain(logger, storePath, chainId); + DevExUtils.GetBlockChain(logger, storePath, chainId); - Block offset = Utils.ParseBlockOffset(chain, block); + Block offset = DevExUtils.ParseBlockOffset(chain, block); _console.Error.WriteLine("The offset block: #{0} {1}.", offset.Index, offset.Hash); Bencodex.Types.Dictionary goldCurrencyStateDict = (Bencodex.Types.Dictionary) @@ -59,7 +59,7 @@ public void Balance( if (address is { } addrStr) { - Address addr = Utils.ParseAddress(addrStr); + Address addr = DevExUtils.ParseAddress(addrStr); FungibleAssetValue balance = chain.GetBalance(addr, gold, offset.Hash); _console.Out.WriteLine("{0}\t{1}", addr, balance); return; diff --git a/NineChronicles.Headless.Executable/Commands/ChainCommand.cs b/NineChronicles.Headless.Executable/Commands/ChainCommand.cs index 80fa864ea..87ea951e8 100644 --- a/NineChronicles.Headless.Executable/Commands/ChainCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/ChainCommand.cs @@ -10,11 +10,9 @@ using Cocona.Help; using Libplanet; using Libplanet.Action; -using Libplanet.Action.Loader; using Libplanet.Blockchain; using Libplanet.Blockchain.Policies; using Libplanet.Blocks; -using Libplanet.Extensions.Cocona; using Libplanet.RocksDBStore; using Libplanet.Store; using Libplanet.Store.Trie; @@ -26,6 +24,7 @@ using NineChronicles.Headless.Executable.Store; using Serilog.Core; using static NineChronicles.Headless.NCActionUtils; +using CoconaUtils = Libplanet.Extensions.Cocona.Utils; namespace NineChronicles.Headless.Executable.Commands { @@ -81,7 +80,7 @@ public void Tip( BlockHash tipHash = store.IndexBlockHash(chainId, -1) ?? throw new CommandExitedException("The given chain seems empty.", -1); Block tip = store.GetBlock(tipHash); - _console.Out.WriteLine(Utils.SerializeHumanReadable(tip.Header)); + _console.Out.WriteLine(CoconaUtils.SerializeHumanReadable(tip.Header)); store.Dispose(); } diff --git a/NineChronicles.Headless.Executable/Commands/GenesisCommand.cs b/NineChronicles.Headless.Executable/Commands/GenesisCommand.cs index a0bf5d42b..2294f7638 100644 --- a/NineChronicles.Headless.Executable/Commands/GenesisCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/GenesisCommand.cs @@ -12,11 +12,11 @@ using Libplanet.Assets; using Libplanet.Blocks; using Libplanet.Crypto; -using Libplanet.Extensions.Cocona; using Nekoyume; using Nekoyume.Action; using Nekoyume.Model.State; using NineChronicles.Headless.Executable.IO; +using CoconaUtils = Libplanet.Extensions.Cocona.Utils; using Lib9cUtils = Lib9c.DevExtensions.Utils; namespace NineChronicles.Headless.Executable.Commands @@ -37,7 +37,7 @@ private void ProcessData(DataConfig config, out Dictionary table _console.Out.WriteLine("\nProcessing data for genesis..."); if (string.IsNullOrEmpty(config.TablePath)) { - throw Utils.Error("TablePath is not set."); + throw CoconaUtils.Error("TablePath is not set."); } tableSheets = Lib9cUtils.ImportSheets(config.TablePath); @@ -273,7 +273,7 @@ public void Mine( } catch (Exception e) { - throw Utils.Error(e.Message); + throw CoconaUtils.Error(e.Message); } } diff --git a/NineChronicles.Headless.Executable/Commands/MarketCommand.cs b/NineChronicles.Headless.Executable/Commands/MarketCommand.cs index 13d348a99..2cfb88f51 100644 --- a/NineChronicles.Headless.Executable/Commands/MarketCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/MarketCommand.cs @@ -5,7 +5,6 @@ using Bencodex; using Bencodex.Types; using Cocona; -using Lib9c.DevExtensions; using Lib9c.Model.Order; using Libplanet; using Libplanet.Assets; @@ -18,6 +17,7 @@ using NineChronicles.Headless.Executable.IO; using Serilog.Core; using static NineChronicles.Headless.NCActionUtils; +using DevExUtils = Lib9c.DevExtensions.Utils; namespace NineChronicles.Headless.Executable.Commands { @@ -59,10 +59,10 @@ public void Query( Guid? chainId = null ) { - using Logger logger = Utils.ConfigureLogger(verbose); + using Logger logger = DevExUtils.ConfigureLogger(verbose); TextWriter stderr = _console.Error; (BlockChain chain, IStore store, _, _) = - Utils.GetBlockChain(logger, storePath, chainId); + DevExUtils.GetBlockChain(logger, storePath, chainId); HashSet? itemTypes = null; if (itemType is { } t) @@ -79,9 +79,9 @@ public void Query( } } - Block start = Utils.ParseBlockOffset(chain, from, defaultIndex: 0); + Block start = DevExUtils.ParseBlockOffset(chain, from, defaultIndex: 0); stderr.WriteLine("The bottom block to search: #{0} {1}.", start.Index, start.Hash); - Block end = Utils.ParseBlockOffset(chain, to); + Block end = DevExUtils.ParseBlockOffset(chain, to); stderr.WriteLine("The topmost block to search: #{0} {1}.", end.Index, end.Hash); Block block = end; diff --git a/NineChronicles.Headless.Executable/Commands/StateCommand.cs b/NineChronicles.Headless.Executable/Commands/StateCommand.cs index 9fb92ec7f..bf368c01e 100644 --- a/NineChronicles.Headless.Executable/Commands/StateCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/StateCommand.cs @@ -23,6 +23,7 @@ using Nekoyume.Action.Loader; using NineChronicles.Headless.Executable.IO; using Serilog.Core; +using DevExUtils = Lib9c.DevExtensions.Utils; namespace NineChronicles.Headless.Executable.Commands { @@ -66,7 +67,7 @@ public void Rebuild( string? useMemoryKvStore = null ) { - using Logger logger = Utils.ConfigureLogger(verbose); + using Logger logger = DevExUtils.ConfigureLogger(verbose); CancellationToken cancellationToken = GetInterruptSignalCancellationToken(); TextWriter stderr = _console.Error; ( @@ -74,14 +75,14 @@ public void Rebuild( IStore store, IKeyValueStore stateKvStore, IStateStore stateStore - ) = Utils.GetBlockChain( + ) = DevExUtils.GetBlockChain( logger, storePath, chainId, useMemoryKvStore is string p ? new MemoryKeyValueStore(p, stderr) : null ); - Block bottom = Utils.ParseBlockOffset(chain, bottommost, 0); - Block top = Utils.ParseBlockOffset(chain, topmost); + Block bottom = DevExUtils.ParseBlockOffset(chain, bottommost, 0); + Block top = DevExUtils.ParseBlockOffset(chain, topmost); stderr.WriteLine("It will execute all actions (tx actions & block actions)"); stderr.WriteLine( @@ -221,7 +222,7 @@ public void Check( bool verbose = false ) { - using Logger logger = Utils.ConfigureLogger(verbose); + using Logger logger = DevExUtils.ConfigureLogger(verbose); CancellationToken cancellationToken = GetInterruptSignalCancellationToken(); TextWriter stderr = _console.Error; ( @@ -229,12 +230,12 @@ public void Check( IStore store, IKeyValueStore stateKvStore, IStateStore stateStore - ) = Utils.GetBlockChain( + ) = DevExUtils.GetBlockChain( logger, storePath, chainId ); - Block checkBlock = Utils.ParseBlockOffset(chain, block); + Block checkBlock = DevExUtils.ParseBlockOffset(chain, block); HashDigest stateRootHash = checkBlock.StateRootHash; ITrie stateRoot = stateStore.GetStateRoot(stateRootHash); bool exist = stateRoot.Recorded; diff --git a/NineChronicles.Headless/GraphTypes/ActionQuery.cs b/NineChronicles.Headless/GraphTypes/ActionQuery.cs index 3ca309ced..502ceb81c 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQuery.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQuery.cs @@ -188,12 +188,13 @@ public ActionQuery(StandaloneContext standaloneContext) var sender = context.GetArgument
("sender"); var recipient = context.GetArgument
("recipient"); var currencyEnum = context.GetArgument("currency"); - if (!standaloneContext.CurrencyFactory?.TryGetCurrency(currencyEnum, out var currency) ?? true) + Currency currency = new Currency(); + if (!standaloneContext.CurrencyFactory?.TryGetCurrency(currencyEnum, out currency) ?? true) { throw new ExecutionError($"Currency {currencyEnum} is not found."); } - var amount = FungibleAssetValue.Parse(currency!.Value, context.GetArgument("amount")); + var amount = FungibleAssetValue.Parse(currency, context.GetArgument("amount")); var memo = context.GetArgument("memo"); ActionBase action = new TransferAsset(sender, recipient, amount, memo); return Encode(context, action); diff --git a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs index 06f847ef9..0c6d3ff2c 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Garages.cs @@ -50,9 +50,9 @@ private void RegisterGarages() fungibleAssetValues = new List<(Address address, FungibleAssetValue fungibleAssetValue)>(); foreach (var (balanceAddr, (currencyTicker, value)) in balanceInputList) { - if (StandaloneContext.TryGetFungibleAssetValue(currencyTicker, value, out var fav)) + if (StandaloneContext.FungibleAssetValueFactory!.TryGetFungibleAssetValue(currencyTicker, value, out var fav)) { - fungibleAssetValues.Add((balanceAddr, fav!.Value)); + fungibleAssetValues.Add((balanceAddr, fav)); } else { @@ -112,9 +112,9 @@ private void RegisterGarages() fungibleAssetValues = new List(); foreach (var (currencyTicker, value) in fungibleAssetValueInputList) { - if (StandaloneContext.TryGetFungibleAssetValue(currencyTicker, value, out var fav)) + if (StandaloneContext.FungibleAssetValueFactory!.TryGetFungibleAssetValue(currencyTicker, value, out var fav)) { - fungibleAssetValues.Add(fav!.Value); + fungibleAssetValues.Add(fav); } else { @@ -173,9 +173,9 @@ private void RegisterGarages() fungibleAssetValues = new List<(Address address, FungibleAssetValue fungibleAssetValue)>(); foreach (var (addr, (currencyTicker, value)) in balanceInputList) { - if (StandaloneContext.TryGetFungibleAssetValue(currencyTicker, value, out var fav)) + if (StandaloneContext.FungibleAssetValueFactory!.TryGetFungibleAssetValue(currencyTicker, value, out var fav)) { - fungibleAssetValues.Add((addr, fav!.Value)); + fungibleAssetValues.Add((addr, fav)); } else { diff --git a/NineChronicles.Headless/GraphTypes/AddressQuery.cs b/NineChronicles.Headless/GraphTypes/AddressQuery.cs index 744c5a715..0336232b9 100644 --- a/NineChronicles.Headless/GraphTypes/AddressQuery.cs +++ b/NineChronicles.Headless/GraphTypes/AddressQuery.cs @@ -92,12 +92,12 @@ public AddressQuery(StandaloneContext standaloneContext) resolve: context => { var currencyEnum = context.GetArgument("currency"); - if (!standaloneContext.TryGetCurrency(currencyEnum, out var currency)) + if (!standaloneContext.CurrencyFactory!.TryGetCurrency(currencyEnum, out var currency)) { throw new ExecutionError($"Currency {currencyEnum} is not found."); } - return currency!.Value.Minters; + return currency.Minters; }); Field>( name: "pledgeAddress", diff --git a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs index cb98ed722..629d24183 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs @@ -56,7 +56,7 @@ public StandaloneQuery(StandaloneContext standaloneContext, IConfiguration confi blockHash switch { BlockHash bh => chain[bh].Index, - null => chain.Tip.Index, + null => chain.Tip!.Index, } ); } diff --git a/NineChronicles.Headless/GraphTypes/States/GaragesType.cs b/NineChronicles.Headless/GraphTypes/States/GaragesType.cs index 15838aee5..ee9d66b7b 100644 --- a/NineChronicles.Headless/GraphTypes/States/GaragesType.cs +++ b/NineChronicles.Headless/GraphTypes/States/GaragesType.cs @@ -15,8 +15,8 @@ public struct Value { public readonly Address AgentAddr; public readonly Address GarageBalancesAddr; - public readonly FungibleAssetValue[] FungibleAssetValues; - public readonly (FungibleItemGarage fungibleItemGarage, Address addr)[] FungibleItemGarages; + internal readonly FungibleAssetValue[] _fungibleAssetValues; + internal readonly (FungibleItemGarage fungibleItemGarage, Address addr)[] _fungibleItemGarages; public Value( Address agentAddr, @@ -26,8 +26,8 @@ public Value( { AgentAddr = agentAddr; GarageBalancesAddr = garageBalancesAddr; - FungibleAssetValues = fungibleAssetValues.ToArray(); - FungibleItemGarages = fungibleItemGarages.ToArray(); + _fungibleAssetValues = fungibleAssetValues.ToArray(); + _fungibleItemGarages = fungibleItemGarages.ToArray(); } } @@ -41,9 +41,9 @@ public GaragesType() resolve: context => context.Source.GarageBalancesAddr); Field>( name: "garageBalances", - resolve: context => context.Source.FungibleAssetValues); + resolve: context => context.Source._fungibleAssetValues); Field>>( name: "fungibleItemGarages", - resolve: context => context.Source.FungibleItemGarages); + resolve: context => context.Source._fungibleItemGarages); } } From 8e4cf66c75517af763df512ae2d981c650cc1237 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Tue, 4 Jul 2023 18:07:08 +0900 Subject: [PATCH 63/89] Bump lib9c:2023q2-iap --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 5d39b5dd7..dd9c1b2cb 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 5d39b5dd783a0786a9181a7701e34b3ad6d3bc52 +Subproject commit dd9c1b2cb32920aa5f0fb36e5ced315f403a5eb8 From e97afa94cdc4cab005cb327a3c3ec1a5aa2796a9 Mon Sep 17 00:00:00 2001 From: XxshiftxX Date: Thu, 6 Jul 2023 11:33:28 +0900 Subject: [PATCH 64/89] Add CombinationSlot query into AvatarStateType --- .../GraphTypes/States/AvatarStateType.cs | 9 +++++++++ .../GraphTypes/States/CombinationSlotStateType.cs | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs b/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs index e8636d5fc..51c521f0c 100644 --- a/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs +++ b/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs @@ -110,6 +110,15 @@ public AvatarStateType() nameof(AvatarState.combinationSlotAddresses), description: "Address list of combination slot.", resolve: context => context.Source.AvatarState.combinationSlotAddresses); + Field>>>( + "combinationSlotState", + description: "Combination slots.", + resolve: context => { + var addresses = context.Source.AvatarState.combinationSlotAddresses; + return context.Source.AccountStateGetter(addresses) + .OfType() + .Select(x => new CombinationSlotState(x)); + }); Field>( nameof(AvatarState.itemMap), description: "List of acquired item ID.", diff --git a/NineChronicles.Headless/GraphTypes/States/CombinationSlotStateType.cs b/NineChronicles.Headless/GraphTypes/States/CombinationSlotStateType.cs index fbc008836..a75473f58 100644 --- a/NineChronicles.Headless/GraphTypes/States/CombinationSlotStateType.cs +++ b/NineChronicles.Headless/GraphTypes/States/CombinationSlotStateType.cs @@ -24,6 +24,10 @@ public CombinationSlotStateType() nameof(CombinationSlotState.StartBlockIndex), description: "Block index at the combination started.", resolve: context => context.Source.StartBlockIndex); + Field( + nameof(CombinationSlotState.PetId), + description: "Pet id used in equipment", + resolve: context => context.Source.PetId); } } } From f47d0c4782a4533e053f2778f06e76ca3dda7589 Mon Sep 17 00:00:00 2001 From: XxshiftxX Date: Thu, 6 Jul 2023 11:33:46 +0900 Subject: [PATCH 65/89] Add testcase for AvatarStateType.CombinationSlot query --- .../Common/Fixtures.cs | 7 +++ .../States/Models/AvatarStateTypeTest.cs | 57 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/NineChronicles.Headless.Tests/Common/Fixtures.cs b/NineChronicles.Headless.Tests/Common/Fixtures.cs index b68b66c44..2c4c37104 100644 --- a/NineChronicles.Headless.Tests/Common/Fixtures.cs +++ b/NineChronicles.Headless.Tests/Common/Fixtures.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Lib9c.Model.Order; using Lib9c.Tests; using Libplanet; @@ -65,6 +66,12 @@ public static ShopState ShopStateFX() return shopState; } + public static readonly Address CombinationSlotAddress = AvatarStateFX.combinationSlotAddresses.First(); + public static readonly CombinationSlotState CombinationSlotStateFx = new( + CombinationSlotAddress, + 1 + ); + public static ShardedShopStateV2 ShardedWeapon0ShopStateV2FX() { Address shardedWeapon0ShopStateV2Address = ShardedShopStateV2.DeriveAddress(ItemSubType.Weapon, "0"); diff --git a/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs b/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs index 694360930..26724b9ea 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Bencodex.Types; using GraphQL.Execution; @@ -53,6 +54,39 @@ public async Task Query(AvatarState avatarState, Dictionary expe Assert.Equal(expected, data); } + [Theory] + [MemberData(nameof(CombinationSlotSteteMembers))] + public async Task QueryWithCombinationSlotState(AvatarState avatarState, Dictionary expected) + { + const string query = @" + { + address + combinationSlotState { + address + unlockBlockIndex + unlockStage + startBlockIndex + petId + } + } + "; + var queryResult = await ExecuteQueryAsync( + query, + source: new AvatarStateType.AvatarStateContext( + avatarState, + addresses => addresses.Select(x => + { + if (x == Fixtures.AvatarAddress) return Fixtures.AvatarStateFX.Serialize(); + if (x == Fixtures.UserAddress) return Fixtures.AgentStateFx.Serialize(); + if (x == Fixtures.CombinationSlotAddress) return Fixtures.CombinationSlotStateFx.Serialize(); + return null; + }).ToList(), + (_, _) => new FungibleAssetValue(), + 0)); + var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; + Assert.Equal(expected, data); + } + public static IEnumerable Members => new List { new object[] @@ -66,5 +100,28 @@ public async Task Query(AvatarState avatarState, Dictionary expe }, }, }; + + public static IEnumerable CombinationSlotSteteMembers = new List() + { + new object[] + { + Fixtures.AvatarStateFX, + new Dictionary + { + ["address"] = Fixtures.AvatarAddress.ToString(), + ["combinationSlotState"] = new object[] + { + new Dictionary + { + ["address"] = Fixtures.CombinationSlotAddress.ToString(), + ["unlockBlockIndex"] = 0, + ["unlockStage"] = 1, + ["startBlockIndex"] = 0, + ["petId"] = null + } + } + } + } + }; } } From b10493aa988c9be6515fd1762ef25e2293a29d7f Mon Sep 17 00:00:00 2001 From: XxshiftxX Date: Thu, 6 Jul 2023 13:03:29 +0900 Subject: [PATCH 66/89] Fix lint error --- .../GraphTypes/States/Models/AvatarStateTypeTest.cs | 12 ++++++------ .../GraphTypes/States/AvatarStateType.cs | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs b/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs index 26724b9ea..b37a9105e 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs @@ -74,19 +74,19 @@ public async Task QueryWithCombinationSlotState(AvatarState avatarState, Diction query, source: new AvatarStateType.AvatarStateContext( avatarState, - addresses => addresses.Select(x => + addresses => addresses.Select(x => x switch { - if (x == Fixtures.AvatarAddress) return Fixtures.AvatarStateFX.Serialize(); - if (x == Fixtures.UserAddress) return Fixtures.AgentStateFx.Serialize(); - if (x == Fixtures.CombinationSlotAddress) return Fixtures.CombinationSlotStateFx.Serialize(); - return null; + _ when x == Fixtures.AvatarAddress => Fixtures.AvatarStateFX.Serialize(), + _ when x == Fixtures.UserAddress => Fixtures.AgentStateFx.Serialize(), + _ when x == Fixtures.CombinationSlotAddress => Fixtures.CombinationSlotStateFx.Serialize(), + _ => null }).ToList(), (_, _) => new FungibleAssetValue(), 0)); var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; Assert.Equal(expected, data); } - + public static IEnumerable Members => new List { new object[] diff --git a/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs b/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs index 51c521f0c..156653d51 100644 --- a/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs +++ b/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs @@ -113,7 +113,8 @@ public AvatarStateType() Field>>>( "combinationSlotState", description: "Combination slots.", - resolve: context => { + resolve: context => + { var addresses = context.Source.AvatarState.combinationSlotAddresses; return context.Source.AccountStateGetter(addresses) .OfType() From 62dd6977e8ce7970fab5727a60ab7f35d315860f Mon Sep 17 00:00:00 2001 From: XxshiftxX Date: Thu, 6 Jul 2023 14:21:43 +0900 Subject: [PATCH 67/89] Fix testcase to test all combinationSlot in avatar --- .../Common/Fixtures.cs | 8 ++-- .../States/Models/AvatarStateTypeTest.cs | 41 ++++++++++++------- .../States/CombinationSlotStateType.cs | 4 +- 3 files changed, 31 insertions(+), 22 deletions(-) diff --git a/NineChronicles.Headless.Tests/Common/Fixtures.cs b/NineChronicles.Headless.Tests/Common/Fixtures.cs index 2c4c37104..030802788 100644 --- a/NineChronicles.Headless.Tests/Common/Fixtures.cs +++ b/NineChronicles.Headless.Tests/Common/Fixtures.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using Lib9c.Model.Order; using Lib9c.Tests; @@ -66,11 +67,8 @@ public static ShopState ShopStateFX() return shopState; } - public static readonly Address CombinationSlotAddress = AvatarStateFX.combinationSlotAddresses.First(); - public static readonly CombinationSlotState CombinationSlotStateFx = new( - CombinationSlotAddress, - 1 - ); + public static readonly List CombinationSlotStatesFx = + AvatarStateFX.combinationSlotAddresses.Select(x => new CombinationSlotState(x, 0)).ToList(); public static ShardedShopStateV2 ShardedWeapon0ShopStateV2FX() { diff --git a/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs b/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs index b37a9105e..37410d04a 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs @@ -74,12 +74,26 @@ public async Task QueryWithCombinationSlotState(AvatarState avatarState, Diction query, source: new AvatarStateType.AvatarStateContext( avatarState, - addresses => addresses.Select(x => x switch + addresses => addresses.Select(x => { - _ when x == Fixtures.AvatarAddress => Fixtures.AvatarStateFX.Serialize(), - _ when x == Fixtures.UserAddress => Fixtures.AgentStateFx.Serialize(), - _ when x == Fixtures.CombinationSlotAddress => Fixtures.CombinationSlotStateFx.Serialize(), - _ => null + if (x == Fixtures.AvatarAddress) + { + return Fixtures.AvatarStateFX.Serialize(); + } + + if (x == Fixtures.UserAddress) + { + return Fixtures.AgentStateFx.Serialize(); + } + + var combinationSlotAddressIndex = + Fixtures.AvatarStateFX.combinationSlotAddresses.FindIndex(address => x == address); + if (combinationSlotAddressIndex > -1) + { + return Fixtures.CombinationSlotStatesFx[combinationSlotAddressIndex].Serialize(); + } + + return null; }).ToList(), (_, _) => new FungibleAssetValue(), 0)); @@ -109,17 +123,14 @@ public async Task QueryWithCombinationSlotState(AvatarState avatarState, Diction new Dictionary { ["address"] = Fixtures.AvatarAddress.ToString(), - ["combinationSlotState"] = new object[] + ["combinationSlotState"] = Fixtures.CombinationSlotStatesFx.Select(x => new Dictionary { - new Dictionary - { - ["address"] = Fixtures.CombinationSlotAddress.ToString(), - ["unlockBlockIndex"] = 0, - ["unlockStage"] = 1, - ["startBlockIndex"] = 0, - ["petId"] = null - } - } + ["address"] = x.address.ToString(), + ["unlockBlockIndex"] = x.UnlockBlockIndex, + ["unlockStage"] = x.UnlockStage, + ["startBlockIndex"] = x.StartBlockIndex, + ["petId"] = x.PetId + }).ToArray(), } } }; diff --git a/NineChronicles.Headless/GraphTypes/States/CombinationSlotStateType.cs b/NineChronicles.Headless/GraphTypes/States/CombinationSlotStateType.cs index a75473f58..b6742f078 100644 --- a/NineChronicles.Headless/GraphTypes/States/CombinationSlotStateType.cs +++ b/NineChronicles.Headless/GraphTypes/States/CombinationSlotStateType.cs @@ -12,7 +12,7 @@ public CombinationSlotStateType() nameof(CombinationSlotState.address), description: "Address of combination slot.", resolve: context => context.Source.address); - Field>( + Field>( nameof(CombinationSlotState.UnlockBlockIndex), description: "Block index at the combination slot can be usable.", resolve: context => context.Source.UnlockBlockIndex); @@ -20,7 +20,7 @@ public CombinationSlotStateType() nameof(CombinationSlotState.UnlockStage), description: "Stage id at the combination slot unlock.", resolve: context => context.Source.UnlockStage); - Field>( + Field>( nameof(CombinationSlotState.StartBlockIndex), description: "Block index at the combination started.", resolve: context => context.Source.StartBlockIndex); From 4048dc9b51bc5158f60fb098df5dc057faea9e45 Mon Sep 17 00:00:00 2001 From: XxshiftxX Date: Thu, 6 Jul 2023 14:25:45 +0900 Subject: [PATCH 68/89] Make name of combinationSlot list plural --- NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs b/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs index 156653d51..f3633e03e 100644 --- a/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs +++ b/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs @@ -111,7 +111,7 @@ public AvatarStateType() description: "Address list of combination slot.", resolve: context => context.Source.AvatarState.combinationSlotAddresses); Field>>>( - "combinationSlotState", + "combinationSlotStates", description: "Combination slots.", resolve: context => { From 6df02ebb45999495b36194af44246a7dae2b575a Mon Sep 17 00:00:00 2001 From: XxshiftxX Date: Thu, 6 Jul 2023 14:37:12 +0900 Subject: [PATCH 69/89] Apply name changes in testcase --- .../GraphTypes/States/Models/AvatarStateTypeTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs b/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs index 37410d04a..1f7a8f8a6 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs @@ -61,7 +61,7 @@ public async Task QueryWithCombinationSlotState(AvatarState avatarState, Diction const string query = @" { address - combinationSlotState { + combinationSlotStates { address unlockBlockIndex unlockStage @@ -123,7 +123,7 @@ public async Task QueryWithCombinationSlotState(AvatarState avatarState, Diction new Dictionary { ["address"] = Fixtures.AvatarAddress.ToString(), - ["combinationSlotState"] = Fixtures.CombinationSlotStatesFx.Select(x => new Dictionary + ["combinationSlotStates"] = Fixtures.CombinationSlotStatesFx.Select(x => new Dictionary { ["address"] = x.address.ToString(), ["unlockBlockIndex"] = x.UnlockBlockIndex, From 0fd17bb3f5cd1a5332d3d0cbbbd8fbcabf1f18b0 Mon Sep 17 00:00:00 2001 From: XxshiftxX Date: Thu, 6 Jul 2023 14:37:29 +0900 Subject: [PATCH 70/89] Apply type change in testcase --- .../GraphTypes/States/Models/CombinationSlotStateTypeTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NineChronicles.Headless.Tests/GraphTypes/States/Models/CombinationSlotStateTypeTest.cs b/NineChronicles.Headless.Tests/GraphTypes/States/Models/CombinationSlotStateTypeTest.cs index a4a73ea6f..a4bd3e850 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/States/Models/CombinationSlotStateTypeTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/States/Models/CombinationSlotStateTypeTest.cs @@ -29,9 +29,9 @@ public async Task Query() var expected = new Dictionary() { ["address"] = address.ToString(), - ["unlockBlockIndex"] = 0, + ["unlockBlockIndex"] = 0L, ["unlockStage"] = 1, - ["startBlockIndex"] = 0, + ["startBlockIndex"] = 0L, }; Assert.Equal(expected, data); } From 5a2c3835e4e03e742fb47fbef9d3f43a2a65f33c Mon Sep 17 00:00:00 2001 From: XxshiftxX Date: Thu, 6 Jul 2023 14:40:48 +0900 Subject: [PATCH 71/89] Change name of CombinationSlotStates into CombinationSlots --- .../GraphTypes/States/Models/AvatarStateTypeTest.cs | 4 ++-- NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs b/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs index 1f7a8f8a6..081f7642c 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs @@ -61,7 +61,7 @@ public async Task QueryWithCombinationSlotState(AvatarState avatarState, Diction const string query = @" { address - combinationSlotStates { + combinationSlots { address unlockBlockIndex unlockStage @@ -123,7 +123,7 @@ public async Task QueryWithCombinationSlotState(AvatarState avatarState, Diction new Dictionary { ["address"] = Fixtures.AvatarAddress.ToString(), - ["combinationSlotStates"] = Fixtures.CombinationSlotStatesFx.Select(x => new Dictionary + ["combinationSlots"] = Fixtures.CombinationSlotStatesFx.Select(x => new Dictionary { ["address"] = x.address.ToString(), ["unlockBlockIndex"] = x.UnlockBlockIndex, diff --git a/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs b/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs index f3633e03e..6476edf12 100644 --- a/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs +++ b/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs @@ -111,7 +111,7 @@ public AvatarStateType() description: "Address list of combination slot.", resolve: context => context.Source.AvatarState.combinationSlotAddresses); Field>>>( - "combinationSlotStates", + "combinationSlots", description: "Combination slots.", resolve: context => { From f09c5ba4b2900523fec8f24abadb8245d82c2848 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Fri, 7 Jul 2023 14:42:15 +0900 Subject: [PATCH 72/89] Bump lib9c:2023q2-iap --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index dd9c1b2cb..bd90fd5fd 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit dd9c1b2cb32920aa5f0fb36e5ced315f403a5eb8 +Subproject commit bd90fd5fdfcf4d8c346f55acef3d772377646af5 From f64c2925c0e06a12fcf45457bd60904a46d0bff9 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Fri, 7 Jul 2023 15:06:16 +0900 Subject: [PATCH 73/89] fix: init missing properties --- NineChronicles.Headless/NineChroniclesNodeService.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NineChronicles.Headless/NineChroniclesNodeService.cs b/NineChronicles.Headless/NineChroniclesNodeService.cs index 9d809ff49..c799cc62b 100644 --- a/NineChronicles.Headless/NineChroniclesNodeService.cs +++ b/NineChronicles.Headless/NineChroniclesNodeService.cs @@ -26,6 +26,7 @@ using Libplanet; using Libplanet.Action; using Libplanet.Assets; +using NineChronicles.Headless.Utils; using StrictRenderer = Libplanet.Blockchain.Renderers.Debug.ValidatingActionRenderer; namespace NineChronicles.Headless @@ -280,6 +281,10 @@ internal void ConfigureContext(StandaloneContext standaloneContext) standaloneContext.BlockChain = Swarm.BlockChain; standaloneContext.Store = Store; standaloneContext.Swarm = Swarm; + standaloneContext.CurrencyFactory = + new CurrencyFactory(standaloneContext.BlockChain.GetStates); + standaloneContext.FungibleAssetValueFactory = + new FungibleAssetValueFactory(standaloneContext.CurrencyFactory); BootstrapEnded.WaitAsync().ContinueWith((task) => { standaloneContext.BootstrapEnded = true; From 54f03a9d65dd71b93dc0e57f69153c464acfd928 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Fri, 7 Jul 2023 15:07:59 +0900 Subject: [PATCH 74/89] fix: assert `StandaloneContext.CurrencyFactory` --- NineChronicles.Headless/GraphTypes/ActionQuery.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/NineChronicles.Headless/GraphTypes/ActionQuery.cs b/NineChronicles.Headless/GraphTypes/ActionQuery.cs index 502ceb81c..d06cded4b 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQuery.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQuery.cs @@ -188,8 +188,7 @@ public ActionQuery(StandaloneContext standaloneContext) var sender = context.GetArgument
("sender"); var recipient = context.GetArgument
("recipient"); var currencyEnum = context.GetArgument("currency"); - Currency currency = new Currency(); - if (!standaloneContext.CurrencyFactory?.TryGetCurrency(currencyEnum, out currency) ?? true) + if (!standaloneContext.CurrencyFactory!.TryGetCurrency(currencyEnum, out var currency)) { throw new ExecutionError($"Currency {currencyEnum} is not found."); } From a3ec4a2eecbd4f7c5a8c6c6b4c3dbbfdf6940a50 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Fri, 7 Jul 2023 15:09:05 +0900 Subject: [PATCH 75/89] fix: remove invalid test --- .../GraphTypes/StateQueryTest.cs | 65 ------------------- 1 file changed, 65 deletions(-) diff --git a/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs index 690ca94c5..61cb34d98 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs @@ -23,70 +23,5 @@ public StateQueryTest() { _codec = new Codec(); } - - [Theory] - [MemberData(nameof(GetMemberDataOfLoadIntoMyGarages))] - public async Task Garage( - IEnumerable<(Address balanceAddr, FungibleAssetValue value)>? fungibleAssetValues, - Address? inventoryAddr, - IEnumerable<(HashDigest fungibleId, int count)>? fungibleIdAndCounts, - string? memo) - { - var expectedAction = new LoadIntoMyGarages( - fungibleAssetValues, - inventoryAddr, - fungibleIdAndCounts, - memo); - var sb = new StringBuilder("{ loadIntoMyGarages("); - if (fungibleAssetValues is not null) - { - sb.Append("fungibleAssetValues: ["); - sb.Append(string.Join(",", fungibleAssetValues.Select(tuple => - $"{{ balanceAddr: \"{tuple.balanceAddr.ToHex()}\", " + - $"value: {{ currencyTicker: \"{tuple.value.Currency.Ticker}\"," + - $"value: \"{tuple.value.GetQuantityString()}\" }} }}"))); - sb.Append("],"); - } - - if (inventoryAddr is not null) - { - sb.Append($"inventoryAddr: \"{inventoryAddr.Value.ToHex()}\","); - } - - if (fungibleIdAndCounts is not null) - { - sb.Append("fungibleIdAndCounts: ["); - sb.Append(string.Join(",", fungibleIdAndCounts.Select(tuple => - $"{{ fungibleId: \"{tuple.fungibleId.ToString()}\", " + - $"count: {tuple.count} }}"))); - sb.Append("],"); - } - - if (memo is not null) - { - sb.Append($"memo: \"{memo}\""); - } - - // Remove last ',' if exists. - if (sb[^1] == ',') - { - sb.Remove(sb.Length - 1, 1); - } - - sb.Append(") }"); - var queryResult = await ExecuteQueryAsync(sb.ToString()); - Assert.Null(queryResult.Errors); - var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; - var plainValue = _codec.Decode(ByteUtil.ParseHex((string)data["loadIntoMyGarages"])); - Assert.IsType(plainValue); - var actionBase = DeserializeNCAction(plainValue); - var actualAction = Assert.IsType(actionBase); - Assert.True(expectedAction.FungibleAssetValues?.SequenceEqual(actualAction.FungibleAssetValues) ?? - actualAction.FungibleAssetValues is null); - Assert.Equal(expectedAction.InventoryAddr, actualAction.InventoryAddr); - Assert.True(expectedAction.FungibleIdAndCounts?.SequenceEqual(actualAction.FungibleIdAndCounts) ?? - actualAction.FungibleIdAndCounts is null); - Assert.Equal(expectedAction.Memo, actualAction.Memo); - } } } From dd02e06d1d71c2b09bf1514b7a9875f638b3e571 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Mon, 10 Jul 2023 13:48:14 +0900 Subject: [PATCH 76/89] fix: correct `garages` state query --- .../GraphTypes/StateQueryTest.cs | 195 ++++++++++++++++++ .../GraphTypes/StateQueryFields/Garages.cs | 96 +++++---- .../GraphTypes/States/GaragesType.cs | 19 +- .../Models/Garage/FungibleItemGarageType.cs | 17 ++ .../States/Models/Item/FungibleItemType.cs | 4 +- .../GraphTypes/States/Models/Item/ItemType.cs | 6 +- .../GraphTypes/WithAddressType.cs | 23 --- 7 files changed, 279 insertions(+), 81 deletions(-) delete mode 100644 NineChronicles.Headless/GraphTypes/WithAddressType.cs diff --git a/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs index 61cb34d98..36e785f62 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; @@ -5,11 +6,21 @@ using System.Threading.Tasks; using Bencodex; using Bencodex.Types; +using Google.Protobuf.WellKnownTypes; +using GraphQL; using GraphQL.Execution; +using Lib9c; using Libplanet; using Libplanet.Assets; +using Libplanet.Crypto; +using Nekoyume; using Nekoyume.Action.Garages; +using Nekoyume.Model.Elemental; +using Nekoyume.Model.Garages; +using Nekoyume.Model.Item; +using Nekoyume.Model.State; using NineChronicles.Headless.GraphTypes; +using NineChronicles.Headless.GraphTypes.States; using Xunit; using static NineChronicles.Headless.Tests.GraphQLTestUtils; @@ -23,5 +34,189 @@ public StateQueryTest() { _codec = new Codec(); } + + [Theory] + [MemberData(nameof(GetMemberDataOfGarages))] + public async Task Garage( + Address agentAddr, + IEnumerable? currencyEnums, + IEnumerable? currencyTickers, + IEnumerable? fungibleItemIds) + { + var sb = new StringBuilder("{ garages("); + sb.Append($"agentAddr: \"{agentAddr.ToString()}\""); + if (currencyEnums is not null) + { + if (currencyTickers is not null) + { + throw new ExecutionError( + "Use either `currencyEnums` or `currencyTickers` to get balances."); + } + + sb.Append(", currencyEnums: ["); + sb.Append(string.Join(", ", currencyEnums)); + sb.Append("]"); + } + else if (currencyTickers is not null) + { + sb.Append(", currencyTickers: ["); + sb.Append(string.Join(", ", currencyTickers.Select(ticker => $"\"{ticker}\""))); + sb.Append("]"); + } + + if (fungibleItemIds is not null) + { + sb.Append(", fungibleItemIds: ["); + sb.Append(string.Join(", ", fungibleItemIds.Select(id => $"\"{id}\""))); + sb.Append("]"); + } + + sb.Append(") {"); + sb.Append("agentAddr"); + sb.Append(" garageBalancesAddr"); + sb.Append(" garageBalances {"); + sb.Append(" currency { ticker } sign majorUnit minorUnit quantity string"); + sb.Append(" }"); + sb.Append(" fungibleItemGarages {"); + sb.Append(" item { fungibleItemId } count"); + sb.Append(" }"); + sb.Append("}}"); + var addrToFungibleItemIdDict = fungibleItemIds is null + ? new Dictionary() + : fungibleItemIds.ToDictionary( + fungibleItemId => Addresses.GetGarageAddress( + agentAddr, + HashDigest.FromString(fungibleItemId)), + fungibleItemId => fungibleItemId); + var queryResult = await ExecuteQueryAsync( + sb.ToString(), + source: new StateContext( + stateAddresses => + { + var arr = new IValue?[stateAddresses.Count]; + for (var i = 0; i < stateAddresses.Count; i++) + { + var stateAddr = stateAddresses[i]; + if (stateAddr.Equals(Addresses.GoldCurrency)) + { + var currency = Currency.Legacy( + "NCG", + 2, + new Address("0x47D082a115c63E7b58B1532d20E631538eaFADde")); + arr[i] = new GoldCurrencyState(currency).Serialize(); + continue; + } + + var fungibleItemId = addrToFungibleItemIdDict[stateAddr]; + var material = new Material(Dictionary.Empty + .SetItem("id", 400_000.Serialize()) + .SetItem("grade", 1.Serialize()) + .SetItem("item_type", ItemType.Material.Serialize()) + .SetItem("item_sub_type", ItemSubType.Hourglass.Serialize()) + .SetItem("elemental_type", ElementalType.Normal.Serialize()) + .SetItem("item_id", HashDigest.FromString(fungibleItemId).Serialize())); + var fig = new FungibleItemGarage(material, 10); + arr[i] = fig.Serialize(); + } + + return arr; + }, + (_, currency) => currency.Ticker switch + { + "NCG" => new FungibleAssetValue(currency, 99, 99), + "CRYSTAL" or "GARAGE" => new FungibleAssetValue( + currency, + 99, + 123456789012345678), + _ => new FungibleAssetValue(currency, 99, 0), + }, + 0L)); + Assert.Null(queryResult.Errors); + var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; + var garages = (Dictionary)data["garages"]; + Assert.Equal(agentAddr.ToString(), garages["agentAddr"]); + Assert.Equal(Addresses.GetGarageBalanceAddress(agentAddr).ToString(), garages["garageBalancesAddr"]); + if (currencyEnums is not null) + { + var garageBalances = ((object[])garages["garageBalances"]).OfType>(); + Assert.Equal(currencyEnums.Count(), garageBalances.Count()); + foreach (var (currencyEnum, garageBalance) in currencyEnums.Zip(garageBalances)) + { + Assert.Equal( + currencyEnum.ToString(), + ((Dictionary)garageBalance["currency"])["ticker"]); + } + } + else if (currencyTickers is not null) + { + var garageBalances = ((object[])garages["garageBalances"]).OfType>(); + Assert.Equal(currencyTickers.Count(), garageBalances.Count()); + foreach (var (currencyTicker, garageBalance) in currencyTickers.Zip(garageBalances)) + { + Assert.Equal( + currencyTicker, + ((Dictionary)garageBalance["currency"])["ticker"]); + } + } + + if (fungibleItemIds is not null) + { + var fungibleItemGarages = + ((object[])garages["fungibleItemGarages"]).OfType>(); + Assert.Equal(fungibleItemIds.Count(), fungibleItemGarages.Count()); + foreach (var (fungibleItemId, fungibleItemGarage) in fungibleItemIds.Zip(fungibleItemGarages)) + { + var actual = ((Dictionary)fungibleItemGarage["item"])["fungibleItemId"]; + Assert.Equal(fungibleItemId, actual); + } + } + } + + private static IEnumerable GetMemberDataOfGarages() + { + var agentAddr = new PrivateKey().ToAddress(); + yield return new object[] + { + agentAddr, + null, + null, + null, + }; + yield return new object[] + { + agentAddr, + new[] { CurrencyEnum.NCG, CurrencyEnum.CRYSTAL, CurrencyEnum.GARAGE }, + null, + null, + }; + yield return new object[] + { + agentAddr, + null, + new[] { "NCG", "CRYSTAL", "GARAGE" }, + null, + }; + yield return new object[] + { + agentAddr, + null, + null, + new[] { new HashDigest().ToString() }, + }; + yield return new object[] + { + agentAddr, + new[] { CurrencyEnum.NCG, CurrencyEnum.CRYSTAL, CurrencyEnum.GARAGE }, + null, + new[] { new HashDigest().ToString() }, + }; + yield return new object[] + { + agentAddr, + null, + new[] { "NCG", "CRYSTAL", "GARAGE" }, + new[] { new HashDigest().ToString() }, + }; + } } } diff --git a/NineChronicles.Headless/GraphTypes/StateQueryFields/Garages.cs b/NineChronicles.Headless/GraphTypes/StateQueryFields/Garages.cs index c033eefb1..a9e6b8af9 100644 --- a/NineChronicles.Headless/GraphTypes/StateQueryFields/Garages.cs +++ b/NineChronicles.Headless/GraphTypes/StateQueryFields/Garages.cs @@ -20,18 +20,25 @@ private void RegisterGarages() { Field( "garages", + description: "Get balances and fungible items in garages.\n" + + "Use either `currencyEnums` or `currencyTickers` to get balances.", arguments: new QueryArguments( new QueryArgument> { Name = "agentAddr", Description = "Agent address to get balances and fungible items in garages", }, - new QueryArgument>> + new QueryArgument>> + { + Name = "currencyEnums", + Description = "List of currency enums to get balances in garages", + }, + new QueryArgument>> { Name = "currencyTickers", Description = "List of currency tickers to get balances in garages", }, - new QueryArgument>> + new QueryArgument>> { Name = "fungibleItemIds", Description = "List of fungible item IDs to get fungible item in garages", @@ -41,62 +48,65 @@ private void RegisterGarages() { var agentAddr = context.GetArgument
("agentAddr"); var garageBalanceAddr = Addresses.GetGarageBalanceAddress(agentAddr); - var currencyTickers = context.GetArgument("currencyTickers"); - var fungibleAssetValues = new List(); - foreach (var currencyTicker in currencyTickers) + var currencyEnums = context.GetArgument("currencyEnums"); + var currencyTickers = context.GetArgument("currencyTickers"); + var garageBalances = new List(); + if (currencyEnums is not null) { - if (!context.Source.CurrencyFactory.TryGetCurrency(currencyTicker, out var currency)) + if (currencyTickers is not null) { - throw new ExecutionError($"Invalid currency ticker: {currencyTicker}"); + throw new ExecutionError( + "Use either `currencyEnums` or `currencyTickers` to get balances."); } - var balance = context.Source.GetBalance(garageBalanceAddr, currency); - fungibleAssetValues.Add(balance); - } + foreach (var currencyEnum in currencyEnums) + { + if (!context.Source.CurrencyFactory.TryGetCurrency(currencyEnum, out var currency)) + { + throw new ExecutionError($"Invalid currency enum: {currencyEnum}"); + } - var materialItemSheetAddr = Addresses.GetSheetAddress(); - var materialItemSheetValue = context.Source.GetState(materialItemSheetAddr); - if (materialItemSheetValue is null) - { - throw new ExecutionError($"{nameof(MaterialItemSheet)} not found: {materialItemSheetAddr}"); + var balance = context.Source.GetBalance(garageBalanceAddr, currency); + garageBalances.Add(balance); + } } - - var materialItemSheet = new MaterialItemSheet(); - materialItemSheet.Set((Text)materialItemSheetValue); - var fungibleItemIdTuples = - context.GetArgument<(string? fungibleItemId, int? itemSheetId)[]>("fungibleItemIds"); - var fungibleItemGarageAddresses = fungibleItemIdTuples - .Select(tuple => + else if (currencyTickers is not null) + { + foreach (var currencyTicker in currencyTickers) { - var (fungibleItemId, itemSheetId) = tuple; - if (fungibleItemId is not null) + if (!context.Source.CurrencyFactory.TryGetCurrency(currencyTicker, out var currency)) { - return Addresses.GetGarageAddress( - agentAddr, - HashDigest.FromString(fungibleItemId)); + throw new ExecutionError($"Invalid currency ticker: {currencyTicker}"); } - if (itemSheetId is not null) - { - var row = materialItemSheet.OrderedList!.FirstOrDefault(r => r.Id == itemSheetId); - if (row is null) - { - throw new ExecutionError($"Invalid item sheet id: {itemSheetId}"); - } + var balance = context.Source.GetBalance(garageBalanceAddr, currency); + garageBalances.Add(balance); + } + } - return Addresses.GetGarageAddress(agentAddr, row.ItemId); - } + IEnumerable<(FungibleItemGarage?, Address)> fungibleItemGarages; + var fungibleItemIds = context.GetArgument("fungibleItemIds"); + if (fungibleItemIds is null) + { + fungibleItemGarages = Enumerable.Empty<(FungibleItemGarage?, Address)>(); + } + else + { + var fungibleItemGarageAddresses = fungibleItemIds + .Select(fungibleItemId => Addresses.GetGarageAddress( + agentAddr, + HashDigest.FromString(fungibleItemId))) + .ToArray(); + fungibleItemGarages = context.Source.GetStates(fungibleItemGarageAddresses) + .Select((value, i) => value is null or Null + ? (null, fungibleItemGarageAddresses[i]) + : (new FungibleItemGarage(value), fungibleItemGarageAddresses[i])); + } - throw new ExecutionError( - $"Invalid argument: {nameof(fungibleItemId)} or {nameof(itemSheetId)} must be specified."); - }) - .ToArray(); - var fungibleItemGarages = context.Source.GetStates(fungibleItemGarageAddresses) - .Select((value, i) => (new FungibleItemGarage(value), fungibleItemGarageAddresses[i])); return new GaragesType.Value( agentAddr, garageBalanceAddr, - fungibleAssetValues, + garageBalances, fungibleItemGarages); } ); diff --git a/NineChronicles.Headless/GraphTypes/States/GaragesType.cs b/NineChronicles.Headless/GraphTypes/States/GaragesType.cs index ee9d66b7b..e1af4b234 100644 --- a/NineChronicles.Headless/GraphTypes/States/GaragesType.cs +++ b/NineChronicles.Headless/GraphTypes/States/GaragesType.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using GraphQL.Types; using Libplanet; using Libplanet.Assets; @@ -15,19 +14,19 @@ public struct Value { public readonly Address AgentAddr; public readonly Address GarageBalancesAddr; - internal readonly FungibleAssetValue[] _fungibleAssetValues; - internal readonly (FungibleItemGarage fungibleItemGarage, Address addr)[] _fungibleItemGarages; + public readonly IEnumerable GarageBalances; + public readonly IEnumerable<(FungibleItemGarage? fungibleItemGarage, Address addr)> FungibleItemGarages; public Value( Address agentAddr, Address garageBalancesAddr, - IEnumerable fungibleAssetValues, - IEnumerable<(FungibleItemGarage fungibleItemGarage, Address addr)> fungibleItemGarages) + IEnumerable garageBalances, + IEnumerable<(FungibleItemGarage? fungibleItemGarage, Address addr)> fungibleItemGarages) { AgentAddr = agentAddr; GarageBalancesAddr = garageBalancesAddr; - _fungibleAssetValues = fungibleAssetValues.ToArray(); - _fungibleItemGarages = fungibleItemGarages.ToArray(); + GarageBalances = garageBalances; + FungibleItemGarages = fungibleItemGarages; } } @@ -41,9 +40,9 @@ public GaragesType() resolve: context => context.Source.GarageBalancesAddr); Field>( name: "garageBalances", - resolve: context => context.Source._fungibleAssetValues); - Field>>( + resolve: context => context.Source.GarageBalances); + Field>( name: "fungibleItemGarages", - resolve: context => context.Source._fungibleItemGarages); + resolve: context => context.Source.FungibleItemGarages); } } diff --git a/NineChronicles.Headless/GraphTypes/States/Models/Garage/FungibleItemGarageType.cs b/NineChronicles.Headless/GraphTypes/States/Models/Garage/FungibleItemGarageType.cs index 6daa6158d..014c1c65d 100644 --- a/NineChronicles.Headless/GraphTypes/States/Models/Garage/FungibleItemGarageType.cs +++ b/NineChronicles.Headless/GraphTypes/States/Models/Garage/FungibleItemGarageType.cs @@ -1,4 +1,6 @@ using GraphQL.Types; +using Libplanet; +using Libplanet.Explorer.GraphTypes; using Nekoyume.Model.Garages; using NineChronicles.Headless.GraphTypes.States.Models.Item; @@ -12,3 +14,18 @@ public FungibleItemGarageType() Field(name: "count", resolve: context => context.Source.Count); } } + +public class FungibleItemGarageWithAddressType : + ObjectGraphType<(FungibleItemGarage? fungibleItemGarage, Address addr)> +{ + public FungibleItemGarageWithAddressType() + { + Field( + name: "item", + resolve: context => context.Source.fungibleItemGarage?.Item); + Field( + name: "count", + resolve: context => context.Source.fungibleItemGarage?.Count); + Field(name: "addr", resolve: context => context.Source.addr); + } +} diff --git a/NineChronicles.Headless/GraphTypes/States/Models/Item/FungibleItemType.cs b/NineChronicles.Headless/GraphTypes/States/Models/Item/FungibleItemType.cs index 1c5805328..58fce6cbb 100644 --- a/NineChronicles.Headless/GraphTypes/States/Models/Item/FungibleItemType.cs +++ b/NineChronicles.Headless/GraphTypes/States/Models/Item/FungibleItemType.cs @@ -3,12 +3,12 @@ namespace NineChronicles.Headless.GraphTypes.States.Models.Item; -public class FungibleItemType : ItemType +public class FungibleItemType : ItemType { public FungibleItemType() { Field>( "fungibleItemId", - resolve: context => context.Source.FungibleId.ToString()); + resolve: context => context.Source?.FungibleId.ToString()); } } diff --git a/NineChronicles.Headless/GraphTypes/States/Models/Item/ItemType.cs b/NineChronicles.Headless/GraphTypes/States/Models/Item/ItemType.cs index 80b4f0aa3..bbecd84f5 100644 --- a/NineChronicles.Headless/GraphTypes/States/Models/Item/ItemType.cs +++ b/NineChronicles.Headless/GraphTypes/States/Models/Item/ItemType.cs @@ -4,19 +4,19 @@ namespace NineChronicles.Headless.GraphTypes.States.Models.Item; -public class ItemType : ObjectGraphType where T : IItem +public class ItemType : ObjectGraphType where T : IItem? { protected ItemType() { Field>( "itemType", description: "Item category.", - resolve: context => context.Source.ItemType + resolve: context => context.Source?.ItemType ); Field>( "itemSubType", description: "Item sub category.", - resolve: context => context.Source.ItemSubType + resolve: context => context.Source?.ItemSubType ); } } diff --git a/NineChronicles.Headless/GraphTypes/WithAddressType.cs b/NineChronicles.Headless/GraphTypes/WithAddressType.cs deleted file mode 100644 index 04219de60..000000000 --- a/NineChronicles.Headless/GraphTypes/WithAddressType.cs +++ /dev/null @@ -1,23 +0,0 @@ -using GraphQL.Types; -using Libplanet; -using Libplanet.Explorer.GraphTypes; - -namespace NineChronicles.Headless.GraphTypes; - -public sealed class WithAddressType : - ObjectGraphType<(TSourceType source, Address addr)> - where TGraphType : IComplexGraphType, new() -{ - public WithAddressType() - { - var t = new TGraphType(); - foreach (var field in t.Fields) - { - AddField(field); - } - - Field( - "address", - resolve: context => context.Source.addr); - } -} From e584b8c742ea092c99230633514474cf6c61b599 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Mon, 10 Jul 2023 14:02:52 +0900 Subject: [PATCH 77/89] fix: linting --- NineChronicles.Headless/StandaloneContext.cs | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/NineChronicles.Headless/StandaloneContext.cs b/NineChronicles.Headless/StandaloneContext.cs index 0ded0c3bb..667b51c93 100644 --- a/NineChronicles.Headless/StandaloneContext.cs +++ b/NineChronicles.Headless/StandaloneContext.cs @@ -25,23 +25,23 @@ public class StandaloneContext public bool BootstrapEnded { get; set; } public bool PreloadEnded { get; set; } public bool IsMining { get; set; } - public ReplaySubject NodeStatusSubject { get; } = new ReplaySubject(1); - public ReplaySubject PreloadStateSubject { get; } = new ReplaySubject(5); + public ReplaySubject NodeStatusSubject { get; } = new(1); + public ReplaySubject PreloadStateSubject { get; } = new(5); - public Subject DifferentAppProtocolVersionEncounterSubject { get; } - = new Subject(); + public Subject DifferentAppProtocolVersionEncounterSubject { get; } = + new(); - public Subject NotificationSubject { get; } = new Subject(); - public Subject NodeExceptionSubject { get; } = new Subject(); + public Subject NotificationSubject { get; } = new(); + public Subject NodeExceptionSubject { get; } = new(); public NineChroniclesNodeService? NineChroniclesNodeService { get; set; } public ConcurrentDictionary statusSubject, ReplaySubject - stateSubject, ReplaySubject balanceSubject)> - AgentAddresses { get; } = new ConcurrentDictionary statusSubject, ReplaySubject stateSubject, ReplaySubject balanceSubject)> + AgentAddresses + { get; } = new ConcurrentDictionary, ReplaySubject, ReplaySubject)>(); - public NodeStatusType NodeStatus => new NodeStatusType(this) + public NodeStatusType NodeStatus => new(this) { BootstrapEnded = BootstrapEnded, PreloadEnded = PreloadEnded, @@ -53,7 +53,7 @@ public class StandaloneContext public Swarm? Swarm { get; internal set; } public CurrencyFactory? CurrencyFactory { get; set; } - + public FungibleAssetValueFactory? FungibleAssetValueFactory { get; set; } internal TimeSpan DifferentAppProtocolVersionEncounterInterval { get; set; } = TimeSpan.FromSeconds(30); From 9a509db16ba7b6be9adda3b6fcbca3f689019113 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Mon, 10 Jul 2023 15:39:28 +0900 Subject: [PATCH 78/89] Bump lib9c to 9e0282e9 --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 014bff434..9e0282e94 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 014bff43408542025269079e4c283d57388c3f84 +Subproject commit 9e0282e94c1627df99fa76ee98ceaf411cbbae66 From 03eca8961adef906206bb00f7532eb0b2d5bc3ee Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Mon, 10 Jul 2023 15:46:09 +0900 Subject: [PATCH 79/89] Accommodate API changes --- .../ActionEvaluationSerializerTest.cs | 6 +- .../ActionContext.cs | 10 +- .../ActionContextMarshaller.cs | 4 +- .../ActionEvaluation.cs | 8 +- .../ActionEvaluationMarshaller.cs | 8 +- .../RemoteActionEvaluator.cs | 40 +++---- .../RemoteBlockChainStates.cs | 2 +- .../Hosting/LibplanetNodeServiceTest.cs | 2 +- .../Commands/ReplayCommand.Privates.cs | 14 +-- .../Commands/ReplayCommand.cs | 4 +- .../Commands/StateCommand.cs | 8 +- .../Common/Actions/EmptyAction.cs | 2 +- .../ActionEvaluationPublisher.cs | 110 +----------------- .../Controllers/GraphQLController.cs | 2 +- .../GraphTypes/StandaloneSubscription.cs | 2 +- .../GraphTypes/TransactionHeadlessQuery.cs | 2 +- 16 files changed, 61 insertions(+), 163 deletions(-) diff --git a/Libplanet.Extensions.ActionEvaluatorCommonComponents.Tests/ActionEvaluationSerializerTest.cs b/Libplanet.Extensions.ActionEvaluatorCommonComponents.Tests/ActionEvaluationSerializerTest.cs index 1aa6bc071..072ffbd1a 100644 --- a/Libplanet.Extensions.ActionEvaluatorCommonComponents.Tests/ActionEvaluationSerializerTest.cs +++ b/Libplanet.Extensions.ActionEvaluatorCommonComponents.Tests/ActionEvaluationSerializerTest.cs @@ -46,8 +46,8 @@ public void Serialization() Assert.Equal(new[] { "one", "two" }, deserialized.Logs); Assert.Equal(addresses[0], deserialized.InputContext.Signer); Assert.Equal(addresses[1], deserialized.InputContext.Miner); - Assert.Equal(Null.Value, deserialized.OutputStates.GetState(addresses[0])); - Assert.Equal((Text)"foo", deserialized.OutputStates.GetState(addresses[1])); - Assert.Equal(new List((Text)"bar"), deserialized.OutputStates.GetState(addresses[2])); + Assert.Equal(Null.Value, deserialized.OutputState.GetState(addresses[0])); + Assert.Equal((Text)"foo", deserialized.OutputState.GetState(addresses[1])); + Assert.Equal(new List((Text)"bar"), deserialized.OutputState.GetState(addresses[2])); } } diff --git a/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionContext.cs b/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionContext.cs index ed15dbf24..8624fcc83 100644 --- a/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionContext.cs +++ b/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionContext.cs @@ -17,7 +17,7 @@ public ActionContext( long blockIndex, int blockProtocolVersion, bool rehearsal, - AccountStateDelta previousStates, + AccountStateDelta previousState, IRandom random, HashDigest? previousStateRootHash, bool blockAction) @@ -29,7 +29,7 @@ public ActionContext( BlockIndex = blockIndex; BlockProtocolVersion = blockProtocolVersion; Rehearsal = rehearsal; - PreviousStates = previousStates; + PreviousState = previousState; Random = random; PreviousStateRootHash = previousStateRootHash; BlockAction = blockAction; @@ -42,8 +42,8 @@ public ActionContext( public long BlockIndex { get; init; } public int BlockProtocolVersion { get; init; } public bool Rehearsal { get; init; } - public AccountStateDelta PreviousStates { get; init; } - IAccountStateDelta IActionContext.PreviousStates => PreviousStates; + public AccountStateDelta PreviousState { get; init; } + IAccountStateDelta IActionContext.PreviousState => PreviousState; public IRandom Random { get; init; } public HashDigest? PreviousStateRootHash { get; init; } public bool BlockAction { get; init; } @@ -68,7 +68,7 @@ public IActionContext GetUnconsumedContext() BlockIndex, BlockProtocolVersion, Rehearsal, - PreviousStates, + PreviousState, new Random(Random.Seed), PreviousStateRootHash, BlockAction); diff --git a/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionContextMarshaller.cs b/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionContextMarshaller.cs index 6438da9ea..48a030db8 100644 --- a/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionContextMarshaller.cs +++ b/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionContextMarshaller.cs @@ -27,7 +27,7 @@ public static Dictionary Marshal(this IActionContext actionContext) .Add("block_protocol_version", actionContext.BlockProtocolVersion) .Add("random_seed", actionContext.Random.Seed) .Add("signer", actionContext.Signer.ToHex()) - .Add("previous_states", AccountStateDeltaMarshaller.Marshal(actionContext.PreviousStates)); + .Add("previous_states", AccountStateDeltaMarshaller.Marshal(actionContext.PreviousState)); if (actionContext.TxId is { } txId) { @@ -57,7 +57,7 @@ txIdValue is Binary txIdBinaryValue previousStateRootHash: dictionary.ContainsKey("previous_state_root_hash") ? new HashDigest(((Binary)dictionary["previous_state_root_hash"]).ByteArray) : null, - previousStates: AccountStateDeltaMarshaller.Unmarshal(dictionary["previous_states"]), + previousState: AccountStateDeltaMarshaller.Unmarshal(dictionary["previous_states"]), random: new Random((Integer)dictionary["random_seed"]) ); } diff --git a/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionEvaluation.cs b/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionEvaluation.cs index 50ef06f57..a2e655376 100644 --- a/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionEvaluation.cs +++ b/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionEvaluation.cs @@ -9,13 +9,13 @@ public class ActionEvaluation : IActionEvaluation public ActionEvaluation( IValue action, ActionContext inputContext, - AccountStateDelta outputStates, + AccountStateDelta outputState, Exception? exception, List logs) { Action = action; InputContext = inputContext; - OutputStates = outputStates; + OutputState = outputState; Exception = exception; Logs = logs; } @@ -23,8 +23,8 @@ public ActionEvaluation( public IValue Action { get; } public ActionContext InputContext { get; } IActionContext IActionEvaluation.InputContext => InputContext; - public AccountStateDelta OutputStates { get; } - IAccountStateDelta IActionEvaluation.OutputStates => OutputStates; + public AccountStateDelta OutputState { get; } + IAccountStateDelta IActionEvaluation.OutputState => OutputState; public Exception? Exception { get; } public List Logs { get; } } diff --git a/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionEvaluationMarshaller.cs b/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionEvaluationMarshaller.cs index 8eac9771e..2addd6de8 100644 --- a/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionEvaluationMarshaller.cs +++ b/Libplanet.Extensions.ActionEvaluatorCommonComponents/ActionEvaluationMarshaller.cs @@ -16,14 +16,14 @@ public static byte[] Serialize(this IActionEvaluation actionEvaluation) public static IEnumerable Marshal(this IEnumerable actionEvaluations) { var actionEvaluationsArray = actionEvaluations.ToArray(); - var outputStates = AccountStateDeltaMarshaller.Marshal(actionEvaluationsArray.Select(aev => aev.OutputStates)); - var previousStates = AccountStateDeltaMarshaller.Marshal(actionEvaluationsArray.Select(aev => aev.InputContext.PreviousStates)); + var outputStates = AccountStateDeltaMarshaller.Marshal(actionEvaluationsArray.Select(aev => aev.OutputState)); + var previousStates = AccountStateDeltaMarshaller.Marshal(actionEvaluationsArray.Select(aev => aev.InputContext.PreviousState)); foreach (var actionEvaluation in actionEvaluationsArray) { yield return Dictionary.Empty .Add("action", actionEvaluation.Action) .Add("logs", new List(actionEvaluation.Logs)) - .Add("output_states", AccountStateDeltaMarshaller.Marshal(actionEvaluation.OutputStates)) + .Add("output_states", AccountStateDeltaMarshaller.Marshal(actionEvaluation.OutputState)) .Add("input_context", ActionContextMarshaller.Marshal(actionEvaluation.InputContext)) .Add("exception", actionEvaluation.Exception?.GetType().FullName is { } typeName ? (Text)typeName : Null.Value); } @@ -34,7 +34,7 @@ public static Dictionary Marshal(this IActionEvaluation actionEvaluation) return Dictionary.Empty .Add("action", actionEvaluation.Action) .Add("logs", new List(actionEvaluation.Logs)) - .Add("output_states", AccountStateDeltaMarshaller.Marshal(actionEvaluation.OutputStates)) + .Add("output_states", AccountStateDeltaMarshaller.Marshal(actionEvaluation.OutputState)) .Add("input_context", ActionContextMarshaller.Marshal(actionEvaluation.InputContext)) .Add("exception", actionEvaluation.Exception?.GetType().FullName is { } typeName ? (Text)typeName : Null.Value); } diff --git a/Libplanet.Extensions.RemoteActionEvaluator/RemoteActionEvaluator.cs b/Libplanet.Extensions.RemoteActionEvaluator/RemoteActionEvaluator.cs index e11364aae..803670334 100644 --- a/Libplanet.Extensions.RemoteActionEvaluator/RemoteActionEvaluator.cs +++ b/Libplanet.Extensions.RemoteActionEvaluator/RemoteActionEvaluator.cs @@ -44,33 +44,33 @@ public IReadOnlyList Evaluate(IPreEvaluationBlock block) { if (i > 0) { - actionEvaluations[i].InputContext.PreviousStates.StateGetter = - actionEvaluations[i - 1].OutputStates.GetStates; - actionEvaluations[i].InputContext.PreviousStates.BalanceGetter = - actionEvaluations[i - 1].OutputStates.GetBalance; - actionEvaluations[i].InputContext.PreviousStates.TotalSupplyGetter = - actionEvaluations[i - 1].OutputStates.GetTotalSupply; - actionEvaluations[i].InputContext.PreviousStates.ValidatorSetGetter = - actionEvaluations[i - 1].OutputStates.GetValidatorSet; + actionEvaluations[i].InputContext.PreviousState.StateGetter = + actionEvaluations[i - 1].OutputState.GetStates; + actionEvaluations[i].InputContext.PreviousState.BalanceGetter = + actionEvaluations[i - 1].OutputState.GetBalance; + actionEvaluations[i].InputContext.PreviousState.TotalSupplyGetter = + actionEvaluations[i - 1].OutputState.GetTotalSupply; + actionEvaluations[i].InputContext.PreviousState.ValidatorSetGetter = + actionEvaluations[i - 1].OutputState.GetValidatorSet; } else { ( - actionEvaluations[i].InputContext.PreviousStates.StateGetter, - actionEvaluations[i].InputContext.PreviousStates.BalanceGetter, - actionEvaluations[i].InputContext.PreviousStates.TotalSupplyGetter, - actionEvaluations[i].InputContext.PreviousStates.ValidatorSetGetter + actionEvaluations[i].InputContext.PreviousState.StateGetter, + actionEvaluations[i].InputContext.PreviousState.BalanceGetter, + actionEvaluations[i].InputContext.PreviousState.TotalSupplyGetter, + actionEvaluations[i].InputContext.PreviousState.ValidatorSetGetter ) = InitializeAccountGettersPair(block); } - actionEvaluations[i].OutputStates.StateGetter = - actionEvaluations[i].InputContext.PreviousStates.GetStates; - actionEvaluations[i].OutputStates.BalanceGetter = - actionEvaluations[i].InputContext.PreviousStates.GetBalance; - actionEvaluations[i].OutputStates.TotalSupplyGetter = - actionEvaluations[i].InputContext.PreviousStates.GetTotalSupply; - actionEvaluations[i].OutputStates.ValidatorSetGetter = - actionEvaluations[i].InputContext.PreviousStates.GetValidatorSet; + actionEvaluations[i].OutputState.StateGetter = + actionEvaluations[i].InputContext.PreviousState.GetStates; + actionEvaluations[i].OutputState.BalanceGetter = + actionEvaluations[i].InputContext.PreviousState.GetBalance; + actionEvaluations[i].OutputState.TotalSupplyGetter = + actionEvaluations[i].InputContext.PreviousState.GetTotalSupply; + actionEvaluations[i].OutputState.ValidatorSetGetter = + actionEvaluations[i].InputContext.PreviousState.GetValidatorSet; } return actionEvaluations; diff --git a/Libplanet.Extensions.RemoteBlockChainStates/RemoteBlockChainStates.cs b/Libplanet.Extensions.RemoteBlockChainStates/RemoteBlockChainStates.cs index 772b0e926..4afd7be39 100644 --- a/Libplanet.Extensions.RemoteBlockChainStates/RemoteBlockChainStates.cs +++ b/Libplanet.Extensions.RemoteBlockChainStates/RemoteBlockChainStates.cs @@ -163,7 +163,7 @@ public ValidatorSet GetValidatorSet(BlockHash? offset) .ToList()); } - public IBlockStates GetBlockStates(BlockHash? offset) + public IBlockState GetBlockState(BlockHash? offset) { throw new NotSupportedException(); } diff --git a/Libplanet.Headless.Tests/Hosting/LibplanetNodeServiceTest.cs b/Libplanet.Headless.Tests/Hosting/LibplanetNodeServiceTest.cs index 22c117fee..a8c644e39 100644 --- a/Libplanet.Headless.Tests/Hosting/LibplanetNodeServiceTest.cs +++ b/Libplanet.Headless.Tests/Hosting/LibplanetNodeServiceTest.cs @@ -91,7 +91,7 @@ private class DummyAction : IAction IAccountStateDelta IAction.Execute(IActionContext context) { - return context.PreviousStates; + return context.PreviousState; } void IAction.LoadPlainValue(IValue plainValue) diff --git a/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs b/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs index d375fef34..3c55116df 100644 --- a/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs +++ b/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs @@ -564,7 +564,7 @@ public ActionContext( Address miner, long blockIndex, int blockProtocolVersion, - IAccountStateDelta previousStates, + IAccountStateDelta previousState, int randomSeed, bool rehearsal = false) { @@ -574,7 +574,7 @@ public ActionContext( BlockIndex = blockIndex; BlockProtocolVersion = blockProtocolVersion; Rehearsal = rehearsal; - PreviousStates = previousStates; + PreviousState = previousState; Random = new Random(randomSeed); _randomSeed = randomSeed; } @@ -591,7 +591,7 @@ public ActionContext( public bool Rehearsal { get; } - public IAccountStateDelta PreviousStates { get; } + public IAccountStateDelta PreviousState { get; } public IRandom Random { get; } @@ -613,7 +613,7 @@ public IActionContext GetUnconsumedContext() => Miner, BlockIndex, BlockProtocolVersion, - PreviousStates, + PreviousState, _randomSeed, Rehearsal); @@ -649,7 +649,7 @@ private static IEnumerable EvaluateActions( ILogger? logger = null) { ActionContext CreateActionContext( - IAccountStateDelta prevStates, + IAccountStateDelta prevState, int randomSeed) { return new ActionContext( @@ -658,7 +658,7 @@ ActionContext CreateActionContext( miner: miner, blockIndex: blockIndex, blockProtocolVersion: blockProtocolVersion, - previousStates: prevStates, + previousState: prevState, randomSeed: randomSeed); } @@ -743,7 +743,7 @@ ActionContext CreateActionContext( yield return new ActionEvaluation( action: action, inputContext: equivalentContext, - outputStates: nextStates, + outputState: nextStates, exception: exc); if (exc is { }) diff --git a/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs b/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs index 14d0d278b..47e3fc2b5 100644 --- a/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs @@ -91,7 +91,7 @@ public int Tx( } // Evaluate tx. - IAccountState previousBlockStates = blockChain.GetBlockStates(previousBlock.Hash); + IAccountState previousBlockStates = blockChain.GetBlockState(previousBlock.Hash); IAccountStateDelta previousStates = AccountStateDelta.Create(previousBlockStates); var actions = tx.Actions.Select(a => ToAction(a)); var actionEvaluations = EvaluateActions( @@ -136,7 +136,7 @@ public int Tx( outputSw?.WriteLine(msg); } - var states = actionEvaluation.OutputStates; + var states = actionEvaluation.OutputState; var addressNum = 1; foreach (var (updatedAddress, updatedState) in states.Delta.States) { diff --git a/NineChronicles.Headless.Executable/Commands/StateCommand.cs b/NineChronicles.Headless.Executable/Commands/StateCommand.cs index 4b239c49a..6f6cb6260 100644 --- a/NineChronicles.Headless.Executable/Commands/StateCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/StateCommand.cs @@ -447,13 +447,13 @@ private static ImmutableDictionary GetTotalDelta( string validatorSetKey) { IImmutableSet
stateUpdatedAddresses = actionEvaluations - .SelectMany(a => a.OutputStates.Delta.StateUpdatedAddresses) + .SelectMany(a => a.OutputState.Delta.StateUpdatedAddresses) .ToImmutableHashSet(); IImmutableSet<(Address, Currency)> updatedFungibleAssets = actionEvaluations - .SelectMany(a => a.OutputStates.Delta.UpdatedFungibleAssets) + .SelectMany(a => a.OutputState.Delta.UpdatedFungibleAssets) .ToImmutableHashSet(); IImmutableSet updatedTotalSupplies = actionEvaluations - .SelectMany(a => a.OutputStates.Delta.UpdatedTotalSupplyCurrencies) + .SelectMany(a => a.OutputState.Delta.UpdatedTotalSupplyCurrencies) .ToImmutableHashSet(); if (actionEvaluations.Count == 0) @@ -461,7 +461,7 @@ private static ImmutableDictionary GetTotalDelta( return ImmutableDictionary.Empty; } - IAccountStateDelta lastStates = actionEvaluations[actionEvaluations.Count - 1].OutputStates; + IAccountStateDelta lastStates = actionEvaluations[actionEvaluations.Count - 1].OutputState; ImmutableDictionary totalDelta = stateUpdatedAddresses.ToImmutableDictionary( diff --git a/NineChronicles.Headless.Tests/Common/Actions/EmptyAction.cs b/NineChronicles.Headless.Tests/Common/Actions/EmptyAction.cs index 95ce3cda2..36d3af788 100644 --- a/NineChronicles.Headless.Tests/Common/Actions/EmptyAction.cs +++ b/NineChronicles.Headless.Tests/Common/Actions/EmptyAction.cs @@ -13,7 +13,7 @@ public void LoadPlainValue(IValue plainValue) public IAccountStateDelta Execute(IActionContext context) { - return context.PreviousStates; + return context.PreviousState; } public void Render(IActionContext context, IAccountStateDelta nextStates) diff --git a/NineChronicles.Headless/ActionEvaluationPublisher.cs b/NineChronicles.Headless/ActionEvaluationPublisher.cs index e71c41280..cdda26dff 100644 --- a/NineChronicles.Headless/ActionEvaluationPublisher.cs +++ b/NineChronicles.Headless/ActionEvaluationPublisher.cs @@ -183,10 +183,7 @@ private sealed class Client : IAsyncDisposable private readonly Address _clientAddress; private IDisposable? _blockSubscribe; - private IDisposable? _reorgSubscribe; - private IDisposable? _reorgEndSubscribe; private IDisposable? _actionEveryRenderSubscribe; - private IDisposable? _actionEveryUnrenderSubscribe; private IDisposable? _everyExceptionSubscribe; private IDisposable? _nodeStatusSubscribe; @@ -248,49 +245,6 @@ await _hub.BroadcastRenderBlockAsync( } } ); - _reorgSubscribe = blockRenderer.ReorgSubject - .SubscribeOn(NewThreadScheduler.Default) - .ObserveOn(NewThreadScheduler.Default) - .Subscribe( - async ev => - { - try - { - await _hub.ReportReorgAsync( - Codec.Encode(ev.OldTip.MarshalBlock()), - Codec.Encode(ev.NewTip.MarshalBlock()), - Codec.Encode(ev.Branchpoint.MarshalBlock()) - ); - } - catch (Exception e) - { - // FIXME add logger as property - Log.Error(e, "Skip broadcasting reorg due to the unexpected exception"); - } - } - ); - - _reorgEndSubscribe = blockRenderer.ReorgEndSubject - .SubscribeOn(NewThreadScheduler.Default) - .ObserveOn(NewThreadScheduler.Default) - .Subscribe( - async ev => - { - try - { - await _hub.ReportReorgEndAsync( - Codec.Encode(ev.OldTip.MarshalBlock()), - Codec.Encode(ev.NewTip.MarshalBlock()), - Codec.Encode(ev.Branchpoint.MarshalBlock()) - ); - } - catch (Exception e) - { - // FIXME add logger as property - Log.Error(e, "Skip broadcasting reorg end due to the unexpected exception"); - } - } - ); _actionEveryRenderSubscribe = actionRenderer.EveryRender() .Where(ContainsAddressToBroadcast) @@ -306,7 +260,7 @@ await _hub.ReportReorgEndAsync( : ev.Action; var extra = new Dictionary(); - var previousStates = ev.PreviousStates; + var previousStates = ev.PreviousState; if (pa is IBattleArenaV1 battleArena) { var enemyAvatarAddress = battleArena.EnemyAvatarAddress; @@ -350,7 +304,7 @@ await _hub.ReportReorgEndAsync( } } - var eval = new NCActionEvaluation(pa, ev.Signer, ev.BlockIndex, ev.OutputStates, ev.Exception, previousStates, ev.RandomSeed, extra); + var eval = new NCActionEvaluation(pa, ev.Signer, ev.BlockIndex, ev.OutputState, ev.Exception, previousStates, ev.RandomSeed, extra); var encoded = MessagePackSerializer.Serialize(eval); var c = new MemoryStream(); await using (var df = new DeflateStream(c, CompressionLevel.Fastest)) @@ -389,59 +343,6 @@ await _hub.ReportReorgEndAsync( } ); - _actionEveryUnrenderSubscribe = actionRenderer.EveryUnrender() - .Where(ContainsAddressToBroadcast) - .SubscribeOn(NewThreadScheduler.Default) - .ObserveOn(NewThreadScheduler.Default) - .Subscribe( - async ev => - { - ActionBase? pa = null; - if (!(ev.Action is RewardGold)) - { - pa = ev.Action; - } - try - { - var eval = new NCActionEvaluation(pa, - ev.Signer, - ev.BlockIndex, - ev.OutputStates, - ev.Exception, - ev.PreviousStates, - ev.RandomSeed, - new Dictionary() - ); - var encoded = MessagePackSerializer.Serialize(eval); - var c = new MemoryStream(); - using (var df = new DeflateStream(c, CompressionLevel.Fastest)) - { - df.Write(encoded, 0, encoded.Length); - } - - var compressed = c.ToArray(); - Log.Information( - "[{ClientAddress}] #{BlockIndex} Broadcasting unrender since the given action {Action}. eval size: {Size}", - _clientAddress, - ev.BlockIndex, - ev.Action.GetType(), - compressed.LongLength - ); - await _hub.BroadcastRenderAsync(compressed); - } - catch (SerializationException se) - { - // FIXME add logger as property - Log.Error(se, "Skip broadcasting unrender since the given action isn't serializable."); - } - catch (Exception e) - { - // FIXME add logger as property - Log.Error(e, "Skip broadcasting unrender due to the unexpected exception"); - } - } - ); - _everyExceptionSubscribe = exceptionRenderer.EveryException() .SubscribeOn(NewThreadScheduler.Default) .ObserveOn(NewThreadScheduler.Default) @@ -492,10 +393,7 @@ await _hub.ReportReorgEndAsync( public async ValueTask DisposeAsync() { _blockSubscribe?.Dispose(); - _reorgSubscribe?.Dispose(); - _reorgEndSubscribe?.Dispose(); _actionEveryRenderSubscribe?.Dispose(); - _actionEveryUnrenderSubscribe?.Dispose(); _everyExceptionSubscribe?.Dispose(); _nodeStatusSubscribe?.Dispose(); await _hub.DisposeAsync(); @@ -510,13 +408,13 @@ private bool ContainsAddressToBroadcast(ActionEvaluation ev) private bool ContainsAddressToBroadcastLocal(ActionEvaluation ev) { - var updatedAddresses = ev.OutputStates.Delta.UpdatedAddresses; + var updatedAddresses = ev.OutputState.Delta.UpdatedAddresses; return _context.AddressesToSubscribe.Any(updatedAddresses.Add(ev.Signer).Contains); } private bool ContainsAddressToBroadcastRemoteClient(ActionEvaluation ev) { - var updatedAddresses = ev.OutputStates.Delta.UpdatedAddresses; + var updatedAddresses = ev.OutputState.Delta.UpdatedAddresses; return TargetAddresses.Any(updatedAddresses.Add(ev.Signer).Contains); } } diff --git a/NineChronicles.Headless/Controllers/GraphQLController.cs b/NineChronicles.Headless/Controllers/GraphQLController.cs index af62ecce5..b15de3a3f 100644 --- a/NineChronicles.Headless/Controllers/GraphQLController.cs +++ b/NineChronicles.Headless/Controllers/GraphQLController.cs @@ -235,7 +235,7 @@ private void NotifyAction(ActionEvaluation eval) return; } Address address = StandaloneContext.NineChroniclesNodeService.MinerPrivateKey.PublicKey.ToAddress(); - if (eval.OutputStates.UpdatedAddresses.Contains(address) || eval.Signer == address) + if (eval.OutputState.Delta.UpdatedAddresses.Contains(address) || eval.Signer == address) { if (eval.Signer == address) { diff --git a/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs b/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs index 25c1e27df..0016b4f5c 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs @@ -354,7 +354,7 @@ private void RenderMonsterCollectionStateSubject(ActionEvaluation eval) var agentState = new AgentState(agentDict); Address deriveAddress = MonsterCollectionState.DeriveAddress(address, agentState.MonsterCollectionRound); var subject = subjects.stateSubject; - if (eval.OutputStates.GetState(deriveAddress) is Dictionary state) + if (eval.OutputState.GetState(deriveAddress) is Dictionary state) { subject.OnNext(new MonsterCollectionState(state)); } diff --git a/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs b/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs index c32e5318a..e9fa2742c 100644 --- a/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs +++ b/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs @@ -177,7 +177,7 @@ public TransactionHeadlessQuery(StandaloneContext standaloneContext) null, null, txSuccess.UpdatedStates - .Select(kv => new KeyValuePair( + .Select(kv => new KeyValuePair( kv.Key, kv.Value)) .ToImmutableDictionary(), From 92ebf9358d408367ee6b34525831ede9a3a3109b Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Mon, 10 Jul 2023 17:17:47 +0900 Subject: [PATCH 80/89] Bump lib9c --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index bd90fd5fd..4ddf794af 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit bd90fd5fdfcf4d8c346f55acef3d772377646af5 +Subproject commit 4ddf794af1947ecf8114594b8283c86bfb7c1781 From 61ad705c9b1e35358ec0e6a539066f524a4aff25 Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Mon, 10 Jul 2023 17:31:05 +0900 Subject: [PATCH 81/89] fix: remove useless validation --- NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs index 36e785f62..d630ad4a2 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs @@ -47,12 +47,6 @@ public async Task Garage( sb.Append($"agentAddr: \"{agentAddr.ToString()}\""); if (currencyEnums is not null) { - if (currencyTickers is not null) - { - throw new ExecutionError( - "Use either `currencyEnums` or `currencyTickers` to get balances."); - } - sb.Append(", currencyEnums: ["); sb.Append(string.Join(", ", currencyEnums)); sb.Append("]"); From 4293f906a870d7999c1e94d4ed9c86dd0e73002b Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Mon, 10 Jul 2023 17:31:19 +0900 Subject: [PATCH 82/89] improve: add more case --- .../GraphTypes/StateQueryTest.cs | 54 +++++++++++++++++-- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs index d630ad4a2..5a7f27673 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs @@ -41,7 +41,8 @@ public async Task Garage( Address agentAddr, IEnumerable? currencyEnums, IEnumerable? currencyTickers, - IEnumerable? fungibleItemIds) + IEnumerable? fungibleItemIds, + IEnumerable? setToNullForFungibleItemGarages) { var sb = new StringBuilder("{ garages("); sb.Append($"agentAddr: \"{agentAddr.ToString()}\""); @@ -51,7 +52,8 @@ public async Task Garage( sb.Append(string.Join(", ", currencyEnums)); sb.Append("]"); } - else if (currencyTickers is not null) + + if (currencyTickers is not null) { sb.Append(", currencyTickers: ["); sb.Append(string.Join(", ", currencyTickers.Select(ticker => $"\"{ticker}\""))); @@ -102,6 +104,13 @@ public async Task Garage( } var fungibleItemId = addrToFungibleItemIdDict[stateAddr]; + var index = fungibleItemIds.ToList().IndexOf(fungibleItemId); + if (index >= 0 && setToNullForFungibleItemGarages?.ElementAt(index) == true) + { + arr[i] = null; + continue; + } + var material = new Material(Dictionary.Empty .SetItem("id", 400_000.Serialize()) .SetItem("grade", 1.Serialize()) @@ -158,10 +167,31 @@ public async Task Garage( var fungibleItemGarages = ((object[])garages["fungibleItemGarages"]).OfType>(); Assert.Equal(fungibleItemIds.Count(), fungibleItemGarages.Count()); - foreach (var (fungibleItemId, fungibleItemGarage) in fungibleItemIds.Zip(fungibleItemGarages)) + if (setToNullForFungibleItemGarages is null) + { + foreach (var (fungibleItemId, fungibleItemGarage) in fungibleItemIds + .Zip(fungibleItemGarages)) + { + var actual = ((Dictionary)fungibleItemGarage["item"])["fungibleItemId"]; + Assert.Equal(fungibleItemId, actual); + } + } + else { - var actual = ((Dictionary)fungibleItemGarage["item"])["fungibleItemId"]; - Assert.Equal(fungibleItemId, actual); + foreach (var ((fungibleItemId, setToNull), fungibleItemGarage) in fungibleItemIds + .Zip(setToNullForFungibleItemGarages) + .Zip(fungibleItemGarages)) + { + if (setToNull) + { + Assert.Null(fungibleItemGarage["item"]); + } + else + { + var actual = ((Dictionary)fungibleItemGarage["item"])["fungibleItemId"]; + Assert.Equal(fungibleItemId, actual); + } + } } } } @@ -175,6 +205,7 @@ private static IEnumerable GetMemberDataOfGarages() null, null, null, + null, }; yield return new object[] { @@ -182,6 +213,7 @@ private static IEnumerable GetMemberDataOfGarages() new[] { CurrencyEnum.NCG, CurrencyEnum.CRYSTAL, CurrencyEnum.GARAGE }, null, null, + null, }; yield return new object[] { @@ -189,6 +221,7 @@ private static IEnumerable GetMemberDataOfGarages() null, new[] { "NCG", "CRYSTAL", "GARAGE" }, null, + null, }; yield return new object[] { @@ -196,6 +229,15 @@ private static IEnumerable GetMemberDataOfGarages() null, null, new[] { new HashDigest().ToString() }, + null, + }; + yield return new object[] + { + agentAddr, + null, + null, + new[] { new HashDigest().ToString() }, + new[] { true }, }; yield return new object[] { @@ -203,6 +245,7 @@ private static IEnumerable GetMemberDataOfGarages() new[] { CurrencyEnum.NCG, CurrencyEnum.CRYSTAL, CurrencyEnum.GARAGE }, null, new[] { new HashDigest().ToString() }, + null, }; yield return new object[] { @@ -210,6 +253,7 @@ private static IEnumerable GetMemberDataOfGarages() null, new[] { "NCG", "CRYSTAL", "GARAGE" }, new[] { new HashDigest().ToString() }, + null, }; } } From 1fff0f06ce6fb051d8b660f2563a6cda5e087eed Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Mon, 10 Jul 2023 19:56:20 +0900 Subject: [PATCH 83/89] feat: improve state query `garages` result schema --- .../GraphTypes/StateQueryTest.cs | 18 +++++++++++++++++- .../GraphTypes/StateQueryFields/Garages.cs | 9 ++++----- .../GraphTypes/States/GaragesType.cs | 7 +++++-- .../Models/Garage/FungibleItemGarageType.cs | 9 +++++++-- 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs index 5a7f27673..e986d1708 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs @@ -74,7 +74,7 @@ public async Task Garage( sb.Append(" currency { ticker } sign majorUnit minorUnit quantity string"); sb.Append(" }"); sb.Append(" fungibleItemGarages {"); - sb.Append(" item { fungibleItemId } count"); + sb.Append(" fungibleItemId addr item { fungibleItemId } count"); sb.Append(" }"); sb.Append("}}"); var addrToFungibleItemIdDict = fungibleItemIds is null @@ -172,6 +172,14 @@ public async Task Garage( foreach (var (fungibleItemId, fungibleItemGarage) in fungibleItemIds .Zip(fungibleItemGarages)) { + var actualFungibleItemId = fungibleItemGarage["fungibleItemId"]; + Assert.Equal(fungibleItemId, actualFungibleItemId); + var actualAddr = fungibleItemGarage["addr"]; + Assert.Equal( + Addresses.GetGarageAddress( + agentAddr, + HashDigest.FromString(fungibleItemId)).ToString(), + actualAddr); var actual = ((Dictionary)fungibleItemGarage["item"])["fungibleItemId"]; Assert.Equal(fungibleItemId, actual); } @@ -182,6 +190,14 @@ public async Task Garage( .Zip(setToNullForFungibleItemGarages) .Zip(fungibleItemGarages)) { + var actualFungibleItemId = fungibleItemGarage["fungibleItemId"]; + Assert.Equal(fungibleItemId, actualFungibleItemId); + var actualAddr = fungibleItemGarage["addr"]; + Assert.Equal( + Addresses.GetGarageAddress( + agentAddr, + HashDigest.FromString(fungibleItemId)).ToString(), + actualAddr); if (setToNull) { Assert.Null(fungibleItemGarage["item"]); diff --git a/NineChronicles.Headless/GraphTypes/StateQueryFields/Garages.cs b/NineChronicles.Headless/GraphTypes/StateQueryFields/Garages.cs index a9e6b8af9..27297564d 100644 --- a/NineChronicles.Headless/GraphTypes/StateQueryFields/Garages.cs +++ b/NineChronicles.Headless/GraphTypes/StateQueryFields/Garages.cs @@ -9,7 +9,6 @@ using Libplanet.Explorer.GraphTypes; using Nekoyume; using Nekoyume.Model.Garages; -using Nekoyume.TableData; using NineChronicles.Headless.GraphTypes.States; namespace NineChronicles.Headless.GraphTypes; @@ -84,11 +83,11 @@ private void RegisterGarages() } } - IEnumerable<(FungibleItemGarage?, Address)> fungibleItemGarages; + IEnumerable<(string, Address, FungibleItemGarage?)> fungibleItemGarages; var fungibleItemIds = context.GetArgument("fungibleItemIds"); if (fungibleItemIds is null) { - fungibleItemGarages = Enumerable.Empty<(FungibleItemGarage?, Address)>(); + fungibleItemGarages = Enumerable.Empty<(string, Address, FungibleItemGarage?)>(); } else { @@ -99,8 +98,8 @@ private void RegisterGarages() .ToArray(); fungibleItemGarages = context.Source.GetStates(fungibleItemGarageAddresses) .Select((value, i) => value is null or Null - ? (null, fungibleItemGarageAddresses[i]) - : (new FungibleItemGarage(value), fungibleItemGarageAddresses[i])); + ? (fungibleItemIds[i], fungibleItemGarageAddresses[i], null) + : (fungibleItemIds[i], fungibleItemGarageAddresses[i], new FungibleItemGarage(value))); } return new GaragesType.Value( diff --git a/NineChronicles.Headless/GraphTypes/States/GaragesType.cs b/NineChronicles.Headless/GraphTypes/States/GaragesType.cs index e1af4b234..1d78d0e38 100644 --- a/NineChronicles.Headless/GraphTypes/States/GaragesType.cs +++ b/NineChronicles.Headless/GraphTypes/States/GaragesType.cs @@ -15,13 +15,16 @@ public struct Value public readonly Address AgentAddr; public readonly Address GarageBalancesAddr; public readonly IEnumerable GarageBalances; - public readonly IEnumerable<(FungibleItemGarage? fungibleItemGarage, Address addr)> FungibleItemGarages; + + public readonly IEnumerable<(string fungibleItemId, Address addr, FungibleItemGarage? fungibleItemGarage)> + FungibleItemGarages; public Value( Address agentAddr, Address garageBalancesAddr, IEnumerable garageBalances, - IEnumerable<(FungibleItemGarage? fungibleItemGarage, Address addr)> fungibleItemGarages) + IEnumerable<(string fungibleItemId, Address addr, FungibleItemGarage? fungibleItemGarage)> + fungibleItemGarages) { AgentAddr = agentAddr; GarageBalancesAddr = garageBalancesAddr; diff --git a/NineChronicles.Headless/GraphTypes/States/Models/Garage/FungibleItemGarageType.cs b/NineChronicles.Headless/GraphTypes/States/Models/Garage/FungibleItemGarageType.cs index 014c1c65d..b7c8258c2 100644 --- a/NineChronicles.Headless/GraphTypes/States/Models/Garage/FungibleItemGarageType.cs +++ b/NineChronicles.Headless/GraphTypes/States/Models/Garage/FungibleItemGarageType.cs @@ -16,16 +16,21 @@ public FungibleItemGarageType() } public class FungibleItemGarageWithAddressType : - ObjectGraphType<(FungibleItemGarage? fungibleItemGarage, Address addr)> + ObjectGraphType<(string fungibleItemId, Address addr, FungibleItemGarage? fungibleItemGarage)> { public FungibleItemGarageWithAddressType() { + Field( + name: "fungibleItemId", + resolve: context => context.Source.fungibleItemId); + Field( + name: "addr", + resolve: context => context.Source.addr); Field( name: "item", resolve: context => context.Source.fungibleItemGarage?.Item); Field( name: "count", resolve: context => context.Source.fungibleItemGarage?.Count); - Field(name: "addr", resolve: context => context.Source.addr); } } From ea6144d9c70bffc5ef5dbc8ade697ed0a6bc8d1f Mon Sep 17 00:00:00 2001 From: Hyun Seungmin Date: Tue, 11 Jul 2023 18:22:50 +0900 Subject: [PATCH 84/89] bump: `lib9c:development` --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 627e12ba3..23e4edaa2 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 627e12ba3dd6ec8463398b6e83f609712bd692b2 +Subproject commit 23e4edaa2d5752fe5a93c913908ea9333c28ea2a From 2acd1db65462f198d289265cbb29e36834ca677b Mon Sep 17 00:00:00 2001 From: Submodule Updater Date: Wed, 12 Jul 2023 06:56:03 +0000 Subject: [PATCH 85/89] Update lib9c submodule to planetarium/lib9c@9edcb3074a58638c47099bf085f65514f45a44e1 This commit was automatically generated by Submodule Updater. --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 23e4edaa2..9edcb3074 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 23e4edaa2d5752fe5a93c913908ea9333c28ea2a +Subproject commit 9edcb3074a58638c47099bf085f65514f45a44e1 From d7fb6aab96406512d2832439125688f83b8b6cad Mon Sep 17 00:00:00 2001 From: Submodule Updater Date: Thu, 13 Jul 2023 02:02:31 +0000 Subject: [PATCH 86/89] Update lib9c submodule to planetarium/lib9c@8ad1d90c77ae2ab5023ecfb592ae0020f92739d2 This commit was automatically generated by Submodule Updater. --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 9edcb3074..8ad1d90c7 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 9edcb3074a58638c47099bf085f65514f45a44e1 +Subproject commit 8ad1d90c77ae2ab5023ecfb592ae0020f92739d2 From 1f85974cdbc5c050561f1a2474044da714505d2e Mon Sep 17 00:00:00 2001 From: Submodule Updater Date: Mon, 17 Jul 2023 02:30:29 +0000 Subject: [PATCH 87/89] Update lib9c submodule to planetarium/lib9c@32a2c7db5699f2e9f9eb4ee58555b101ca0d4d74 This commit was automatically generated by Submodule Updater. --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 8ad1d90c7..32a2c7db5 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 8ad1d90c77ae2ab5023ecfb592ae0020f92739d2 +Subproject commit 32a2c7db5699f2e9f9eb4ee58555b101ca0d4d74 From e2500ebb54b071a328bed18a0545ee1ae7203340 Mon Sep 17 00:00:00 2001 From: Submodule Updater Date: Tue, 18 Jul 2023 00:04:34 +0000 Subject: [PATCH 88/89] Update lib9c submodule to planetarium/lib9c@ddb70d8e160030a8f3cdb8ba264f3b7ba69a6bc2 This commit was automatically generated by Submodule Updater. --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 32a2c7db5..ddb70d8e1 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 32a2c7db5699f2e9f9eb4ee58555b101ca0d4d74 +Subproject commit ddb70d8e160030a8f3cdb8ba264f3b7ba69a6bc2 From 9d0a37e95519c890789d2885a83bbe684223baed Mon Sep 17 00:00:00 2001 From: Submodule Updater Date: Wed, 19 Jul 2023 06:53:15 +0000 Subject: [PATCH 89/89] Update lib9c submodule to planetarium/lib9c@f76932c200a7766cedb7e15d4c40e4bdded0c504 This commit was automatically generated by Submodule Updater. --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index ddb70d8e1..f76932c20 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit ddb70d8e160030a8f3cdb8ba264f3b7ba69a6bc2 +Subproject commit f76932c200a7766cedb7e15d4c40e4bdded0c504