From 9d04a7bee99b4f71eceff3c7a4af64783c54311e Mon Sep 17 00:00:00 2001 From: s2quake Date: Thu, 5 Dec 2024 15:34:53 +0900 Subject: [PATCH 1/2] feat: Add state query for delegation --- .../GraphTypes/StateQuery.cs | 19 +++++ .../GraphTypes/ValidatorType.cs | 74 +++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 NineChronicles.Headless/GraphTypes/ValidatorType.cs diff --git a/NineChronicles.Headless/GraphTypes/StateQuery.cs b/NineChronicles.Headless/GraphTypes/StateQuery.cs index 7f07af304..302c9a817 100644 --- a/NineChronicles.Headless/GraphTypes/StateQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StateQuery.cs @@ -772,6 +772,25 @@ public StateQuery() return share.ToString(); } ); + + Field( + name: "validator", + description: "State for validator.", + arguments: new QueryArguments( + new QueryArgument> + { + Name = "validatorAddress", + Description = "Address of validator." + } + ), + resolve: context => + { + var validatorAddress = context.GetArgument
("validatorAddress"); + var repository = new ValidatorRepository(new World(context.Source.WorldState), new HallowActionContext { }); + var delegatee = repository.GetValidatorDelegatee(validatorAddress); + return ValidatorType.FromDelegatee(delegatee); + } + ); } public static List GetRuneOptions( diff --git a/NineChronicles.Headless/GraphTypes/ValidatorType.cs b/NineChronicles.Headless/GraphTypes/ValidatorType.cs new file mode 100644 index 000000000..171d4f1ae --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/ValidatorType.cs @@ -0,0 +1,74 @@ +using System.Numerics; +using GraphQL.Types; +using Libplanet.Types.Assets; +using Nekoyume.ValidatorDelegation; + +namespace NineChronicles.Headless.GraphTypes; + +public class ValidatorType : ObjectGraphType +{ + public BigInteger Power { get; set; } + + public bool IsActive { get; set; } + + public BigInteger TotalShares { get; set; } + + public bool Jailed { get; set; } + + public long JailedUntil { get; set; } + + public bool Tombstoned { get; set; } + + public FungibleAssetValue TotalDelegated { get; set; } + + public BigInteger CommissionPercentage { get; set; } + + public ValidatorType() + { + Field>( + nameof(Power), + description: "Power of validator", + resolve: context => context.Source.Power.ToString("N0")); + Field>( + nameof(IsActive), + description: "Specifies whether the validator is active.", + resolve: context => context.Source.IsActive); + Field>( + nameof(TotalShares), + description: "Total shares of validator", + resolve: context => context.Source.TotalShares.ToString("N0")); + Field>( + nameof(Jailed), + description: "Specifies whether the validator is jailed.", + resolve: context => context.Source.Jailed); + Field>( + nameof(JailedUntil), + description: "Block height until which the validator is jailed.", + resolve: context => context.Source.JailedUntil); + Field>( + nameof(Tombstoned), + description: "Specifies whether the validator is tombstoned.", + resolve: context => context.Source.Tombstoned); + Field>( + nameof(TotalDelegated), + description: "Total delegated amount of the validator.", + resolve: context => context.Source.TotalDelegated); + Field>( + nameof(CommissionPercentage), + description: "Commission percentage of the validator.", + resolve: context => context.Source.CommissionPercentage.ToString("N0")); + } + + public static ValidatorType FromDelegatee(ValidatorDelegatee validatorDelegatee) => + new ValidatorType + { + Power = validatorDelegatee.Power, + IsActive = validatorDelegatee.IsActive, + TotalShares = validatorDelegatee.TotalShares, + Jailed = validatorDelegatee.Jailed, + JailedUntil = validatorDelegatee.JailedUntil, + Tombstoned = validatorDelegatee.Tombstoned, + TotalDelegated = validatorDelegatee.TotalDelegated, + CommissionPercentage = validatorDelegatee.CommissionPercentage, + }; +} From 5a1dab659797e30f95014db12fe7b51884d0c6c2 Mon Sep 17 00:00:00 2001 From: s2quake Date: Thu, 5 Dec 2024 18:07:50 +0900 Subject: [PATCH 2/2] test: StateQuery test code for validator --- .../GraphTypes/ValidatorTypeTest.cs | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 NineChronicles.Headless.Tests/GraphTypes/ValidatorTypeTest.cs diff --git a/NineChronicles.Headless.Tests/GraphTypes/ValidatorTypeTest.cs b/NineChronicles.Headless.Tests/GraphTypes/ValidatorTypeTest.cs new file mode 100644 index 000000000..e96e125e5 --- /dev/null +++ b/NineChronicles.Headless.Tests/GraphTypes/ValidatorTypeTest.cs @@ -0,0 +1,78 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; +using GraphQL.Execution; +using Libplanet.Types.Tx; +using Nekoyume.ValidatorDelegation; +using Xunit; +using Xunit.Abstractions; + +namespace NineChronicles.Headless.Tests.GraphTypes +{ + public class ValidatorTypeTest : GraphQLTestBase + { + public ValidatorTypeTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public async Task ExecuteQuery() + { + // Given + var block = BlockChain.Tip; + var blockHeight = 0L; + var tip = new Domain.Model.BlockChain.Block( + Hash: block.Hash, + PreviousHash: null, + Miner: ProposerPrivateKey.Address, + Index: blockHeight, + Timestamp: block.Timestamp, + StateRootHash: block.StateRootHash, + Transactions: ImmutableArray.Empty); + var worldState = BlockChain.GetNextWorldState(); + var stateRootHash = block.StateRootHash; + var validatorAddress = ProposerPrivateKey.Address.ToHex(); + var query = + $"query {{\n" + + $" stateQuery(index: 0) {{\n" + + $" validator(validatorAddress: \"{validatorAddress}\") {{\n" + + $" power\n" + + $" isActive\n" + + $" totalShares\n" + + $" jailed\n" + + $" jailedUntil\n" + + $" tombstoned\n" + + $" commissionPercentage\n" + + $" totalDelegated {{\n" + + $" currency\n" + + $" quantity\n" + + $" }}\n" + + $" }}\n" + + $" }}\n" + + $"}}\n"; + + BlockChainRepository.Setup(repository => repository.GetBlock(blockHeight)).Returns(tip); + WorldStateRepository.Setup(repository => repository.GetWorldState(stateRootHash)).Returns(worldState); + + // When + var result = await ExecuteQueryAsync(query); + + // Then + var data = (Dictionary)((ExecutionNode)result.Data!).ToValue()!; + var stateQueryResult = (Dictionary)data["stateQuery"]; + var validatorResult = (Dictionary)stateQueryResult["validator"]; + + Assert.Equal("10,000,000,000,000,000,000", validatorResult["power"]); + Assert.Equal(true, validatorResult["isActive"]); + Assert.Equal("10,000,000,000,000,000,000", validatorResult["totalShares"]); + Assert.Equal(false, validatorResult["jailed"]); + Assert.Equal(-1L, validatorResult["jailedUntil"]); + Assert.Equal(false, validatorResult["tombstoned"]); + Assert.Equal($"{ValidatorDelegatee.DefaultCommissionPercentage}", validatorResult["commissionPercentage"]); + + var totalDelegatedResult = (Dictionary)validatorResult["totalDelegated"]; + Assert.Equal("GUILD_GOLD", totalDelegatedResult["currency"]); + Assert.Equal("10.000000000000000000", totalDelegatedResult["quantity"]); + } + } +}