From e4c0db0113e283ef7c6ddec34289e6d9dd345b26 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Tue, 12 Dec 2023 22:06:29 +0900 Subject: [PATCH 1/5] Added BlockHashType and TxIdType; refactored AddressType --- Libplanet.Explorer/GraphTypes/AddressType.cs | 51 +++++--- .../GraphTypes/BlockCommitType.cs | 4 +- .../GraphTypes/BlockHashType.cs | 60 +++++++++ Libplanet.Explorer/GraphTypes/BlockType.cs | 4 +- .../GraphTypes/TransactionType.cs | 5 +- Libplanet.Explorer/GraphTypes/TxIdType.cs | 60 +++++++++ Libplanet.Explorer/Queries/BlockQuery.cs | 16 +-- Libplanet.Explorer/Queries/StateQuery.cs | 123 +++++------------- .../Queries/TransactionQuery.cs | 11 +- 9 files changed, 202 insertions(+), 132 deletions(-) create mode 100644 Libplanet.Explorer/GraphTypes/BlockHashType.cs create mode 100644 Libplanet.Explorer/GraphTypes/TxIdType.cs diff --git a/Libplanet.Explorer/GraphTypes/AddressType.cs b/Libplanet.Explorer/GraphTypes/AddressType.cs index eaa367d829f..9249141176e 100644 --- a/Libplanet.Explorer/GraphTypes/AddressType.cs +++ b/Libplanet.Explorer/GraphTypes/AddressType.cs @@ -1,5 +1,3 @@ -#nullable disable -using System; using GraphQL.Language.AST; using GraphQL.Types; using Libplanet.Crypto; @@ -10,41 +8,54 @@ public class AddressType : StringGraphType { public AddressType() { - Name = "Address"; + Name = "address"; } - public override object Serialize(object value) + public override object? ParseLiteral(IValue value) { - if (value is Address addr) + if (value is StringValue stringValue) { - return addr.ToString(); + return ParseValue(stringValue.Value); } - return value; + if (value is NullValue) + { + return null; + } + + return ThrowLiteralConversionError(value); } - public override object ParseValue(object value) + public override object? ParseValue(object? value) { - switch (value) + if (value is null) + { + return null; + } + + if (value is string str) { - case null: - return null; - case string hex: - return new Address(hex); - default: - throw new ArgumentException( - $"Expected a hexadecimal string but {value}", nameof(value)); + // NOTE: 0x-prefixed *and* 0x-non-prefixed version should both be allowed. + return new Address(str); } + + return ThrowValueConversionError(value); } - public override object ParseLiteral(IValue value) + public override object? Serialize(object? value) { - if (value is StringValue) + if (value is null) + { + return null; + } + + if (value is Address address) { - return ParseValue(value.Value); + // NOTE: 0x-prefixed format is preferred as output. + return address.ToString(); } - return null; + return ThrowSerializationError(value); } } } diff --git a/Libplanet.Explorer/GraphTypes/BlockCommitType.cs b/Libplanet.Explorer/GraphTypes/BlockCommitType.cs index da51afd2c9d..cd26801ff58 100644 --- a/Libplanet.Explorer/GraphTypes/BlockCommitType.cs +++ b/Libplanet.Explorer/GraphTypes/BlockCommitType.cs @@ -19,10 +19,10 @@ public BlockCommitType() description: "The round of the block commit.", resolve: x => x.Source.Round ); - Field>( + Field>( name: "BlockHash", description: "The hash of the block which contains block commit.", - resolve: ctx => ctx.Source.BlockHash.ToString() + resolve: ctx => ctx.Source.BlockHash ); Field>>>( name: "Votes", diff --git a/Libplanet.Explorer/GraphTypes/BlockHashType.cs b/Libplanet.Explorer/GraphTypes/BlockHashType.cs new file mode 100644 index 00000000000..042c0e5c912 --- /dev/null +++ b/Libplanet.Explorer/GraphTypes/BlockHashType.cs @@ -0,0 +1,60 @@ +using GraphQL.Language.AST; +using GraphQL.Types; +using Libplanet.Common; +using Libplanet.Types.Blocks; + +namespace Libplanet.Explorer.GraphTypes +{ + public class BlockHashType : StringGraphType + { + public BlockHashType() + { + Name = "blockHash"; + } + + public override object? ParseLiteral(IValue value) + { + if (value is StringValue stringValue) + { + return ParseValue(stringValue.Value); + } + + if (value is NullValue) + { + return null; + } + + return ThrowLiteralConversionError(value); + } + + public override object? ParseValue(object? value) + { + if (value is null) + { + return null; + } + + if (value is string str) + { + return new BlockHash(ByteUtil.ParseHex(str)); + } + + return ThrowValueConversionError(value); + } + + public override object? Serialize(object? value) + { + if (value is null) + { + return null; + } + + if (value is BlockHash blockHash) + { + return ByteUtil.Hex(blockHash.ByteArray); + } + + return ThrowSerializationError(value); + } + } +} diff --git a/Libplanet.Explorer/GraphTypes/BlockType.cs b/Libplanet.Explorer/GraphTypes/BlockType.cs index 8e0c5f5121d..4b737df4de8 100644 --- a/Libplanet.Explorer/GraphTypes/BlockType.cs +++ b/Libplanet.Explorer/GraphTypes/BlockType.cs @@ -9,10 +9,10 @@ public class BlockType : ObjectGraphType public BlockType(IBlockChainContext context) { // We need multiple row of description for clearer, not confusing explanation of field. - Field>( + Field>( "Hash", description: "A block's hash.", - resolve: ctx => ctx.Source.Hash.ToString() + resolve: ctx => ctx.Source.Hash ); Field>( name: "Index", diff --git a/Libplanet.Explorer/GraphTypes/TransactionType.cs b/Libplanet.Explorer/GraphTypes/TransactionType.cs index ce99e2a132e..90f06bc46f6 100644 --- a/Libplanet.Explorer/GraphTypes/TransactionType.cs +++ b/Libplanet.Explorer/GraphTypes/TransactionType.cs @@ -11,10 +11,11 @@ public class TransactionType : ObjectGraphType { public TransactionType(IBlockChainContext context) { - Field>( + Field>( name: "Id", description: "A unique identifier derived from this transaction content.", - resolve: ctx => ctx.Source.Id.ToString()); + resolve: ctx => ctx.Source.Id + ); Field>( name: "Nonce", description: "The number of previous transactions committed by the signer of " + diff --git a/Libplanet.Explorer/GraphTypes/TxIdType.cs b/Libplanet.Explorer/GraphTypes/TxIdType.cs new file mode 100644 index 00000000000..86b4e21d37a --- /dev/null +++ b/Libplanet.Explorer/GraphTypes/TxIdType.cs @@ -0,0 +1,60 @@ +using GraphQL.Language.AST; +using GraphQL.Types; +using Libplanet.Common; +using Libplanet.Types.Tx; + +namespace Libplanet.Explorer.GraphTypes +{ + public class TxIdType : StringGraphType + { + public TxIdType() + { + Name = "txId"; + } + + public override object? ParseLiteral(IValue value) + { + if (value is StringValue stringValue) + { + return ParseValue(stringValue.Value); + } + + if (value is NullValue) + { + return null; + } + + return ThrowLiteralConversionError(value); + } + + public override object? ParseValue(object? value) + { + if (value is null) + { + return null; + } + + if (value is string str) + { + return new TxId(ByteUtil.ParseHex(str)); + } + + return ThrowValueConversionError(value); + } + + public override object? Serialize(object? value) + { + if (value is null) + { + return null; + } + + if (value is TxId txId) + { + return ByteUtil.Hex(txId.ByteArray); + } + + return ThrowSerializationError(value); + } + } +} diff --git a/Libplanet.Explorer/Queries/BlockQuery.cs b/Libplanet.Explorer/Queries/BlockQuery.cs index 7eca41e236c..fb1fab91f30 100644 --- a/Libplanet.Explorer/Queries/BlockQuery.cs +++ b/Libplanet.Explorer/Queries/BlockQuery.cs @@ -46,13 +46,13 @@ public BlockQuery() Field( "block", arguments: new QueryArguments( - new QueryArgument { Name = "hash" }, - new QueryArgument { Name = "index" } + new QueryArgument { Name = "hash" }, + new QueryArgument { Name = "index" } ), resolve: context => { - string hash = context.GetArgument("hash"); - long? index = context.GetArgument("index", null); + BlockHash? hash = context.GetArgument("hash"); + long? index = context.GetArgument("index"); if (!(hash is null ^ index is null)) { @@ -61,14 +61,14 @@ public BlockQuery() "give only one at a time."); } - if (hash is string hashNotNull) + if (hash is { } nonNullHash) { - return ExplorerQuery.GetBlockByHash(BlockHash.FromString(hashNotNull)); + return ExplorerQuery.GetBlockByHash(nonNullHash); } - if (index is long indexNotNull) + if (index is { } nonNullIndex) { - return ExplorerQuery.GetBlockByIndex(indexNotNull); + return ExplorerQuery.GetBlockByIndex(nonNullIndex); } throw new GraphQL.ExecutionError("Unexpected block query"); diff --git a/Libplanet.Explorer/Queries/StateQuery.cs b/Libplanet.Explorer/Queries/StateQuery.cs index 4b2e9ce4e52..1958129a4cd 100644 --- a/Libplanet.Explorer/Queries/StateQuery.cs +++ b/Libplanet.Explorer/Queries/StateQuery.cs @@ -1,4 +1,3 @@ -using System; using System.Security.Cryptography; using GraphQL; using GraphQL.Types; @@ -23,7 +22,7 @@ public StateQuery() arguments: new QueryArguments( new QueryArgument>>> { Name = "addresses" }, - new QueryArgument { Name = "offsetBlockHash" }, + new QueryArgument { Name = "offsetBlockHash" }, new QueryArgument { Name = "offsetStateRootHash" } ), resolve: ResolveStates @@ -33,7 +32,7 @@ public StateQuery() arguments: new QueryArguments( new QueryArgument> { Name = "owner" }, new QueryArgument> { Name = "currency" }, - new QueryArgument { Name = "offsetBlockHash" }, + new QueryArgument { Name = "offsetBlockHash" }, new QueryArgument { Name = "offsetStateRootHash" } ), resolve: ResolveBalance @@ -42,7 +41,7 @@ public StateQuery() "totalSupply", arguments: new QueryArguments( new QueryArgument> { Name = "currency" }, - new QueryArgument { Name = "offsetBlockHash" }, + new QueryArgument { Name = "offsetBlockHash" }, new QueryArgument { Name = "offsetStateRootHash" } ), resolve: ResolveTotalSupply @@ -50,7 +49,7 @@ public StateQuery() Field>>( "validators", arguments: new QueryArguments( - new QueryArgument { Name = "offsetBlockHash" }, + new QueryArgument { Name = "offsetBlockHash" }, new QueryArgument { Name = "offsetStateRootHash" } ), resolve: ResolveValidatorSet @@ -61,7 +60,7 @@ private static object ResolveStates( IResolveFieldContext<(IBlockChainStates ChainStates, IBlockPolicy Policy)> context) { Address[] addresses = context.GetArgument("addresses"); - string? offsetBlockHash = context.GetArgument("offsetBlockHash"); + BlockHash? offsetBlockHash = context.GetArgument("offsetBlockHash"); HashDigest? offsetStateRootHash = context .GetArgument?>("offsetStateRootHash"); @@ -77,28 +76,15 @@ private static object ResolveStates( ); case (blockhash: not null, _): { - BlockHash offset; - try - { - offset = BlockHash.FromString(offsetBlockHash); - } - catch (Exception e) - { - throw new ExecutionError( - "offsetBlockHash must consist of hexadecimal digits.\n" + e.Message, - e - ); - } - - return context.Source.ChainStates.GetAccountState( - offset - ).GetStates(addresses); + return context.Source.ChainStates + .GetAccountState(offsetBlockHash) + .GetStates(addresses); } case (_, srh: not null): - return context.Source.ChainStates.GetAccountState( - offsetStateRootHash - ).GetStates(addresses); + return context.Source.ChainStates + .GetAccountState(offsetStateRootHash) + .GetStates(addresses); } } @@ -107,7 +93,7 @@ private static object ResolveBalance( { Address owner = context.GetArgument
("owner"); Currency currency = context.GetArgument("currency"); - string? offsetBlockHash = context.GetArgument("offsetBlockHash"); + BlockHash? offsetBlockHash = context.GetArgument("offsetBlockHash"); HashDigest? offsetStateRootHash = context .GetArgument?>("offsetStateRootHash"); @@ -123,28 +109,15 @@ private static object ResolveBalance( ); case (blockhash: not null, _): { - BlockHash offset; - try - { - offset = BlockHash.FromString(offsetBlockHash); - } - catch (Exception e) - { - throw new ExecutionError( - "offsetBlockHash must consist of hexadecimal digits.\n" + e.Message, - e - ); - } - - return context.Source.ChainStates.GetAccountState( - offset - ).GetBalance(owner, currency); + return context.Source.ChainStates + .GetAccountState(offsetBlockHash) + .GetBalance(owner, currency); } case (_, srh: not null): - return context.Source.ChainStates.GetAccountState( - offsetStateRootHash - ).GetBalance(owner, currency); + return context.Source.ChainStates + .GetAccountState(offsetStateRootHash) + .GetBalance(owner, currency); } } @@ -152,7 +125,7 @@ private static object ResolveBalance( IResolveFieldContext<(IBlockChainStates ChainStates, IBlockPolicy Policy)> context) { Currency currency = context.GetArgument("currency"); - string? offsetBlockHash = context.GetArgument("offsetBlockHash"); + BlockHash? offsetBlockHash = context.GetArgument("offsetBlockHash"); HashDigest? offsetStateRootHash = context .GetArgument?>("offsetStateRootHash"); @@ -176,36 +149,20 @@ private static object ResolveBalance( "Either offsetBlockHash or offsetStateRootHash must be specified." ); case (blockhash: not null, _): - { - BlockHash offset; - try - { - offset = BlockHash.FromString(offsetBlockHash); - } - catch (Exception e) - { - throw new ExecutionError( - "offsetBlockHash must consist of hexadecimal digits.\n" + e.Message, - e - ); - } - - return context.Source.ChainStates.GetAccountState( - offset - ).GetTotalSupply(currency); - } - + return context.Source.ChainStates + .GetAccountState(offsetBlockHash) + .GetTotalSupply(currency); case (_, srh: not null): - return context.Source.ChainStates.GetAccountState( - offsetStateRootHash - ).GetTotalSupply(currency); + return context.Source.ChainStates + .GetAccountState(offsetStateRootHash) + .GetTotalSupply(currency); } } private static object? ResolveValidatorSet( IResolveFieldContext<(IBlockChainStates ChainStates, IBlockPolicy Policy)> context) { - string? offsetBlockHash = context.GetArgument("offsetBlockHash"); + BlockHash? offsetBlockHash = context.GetArgument("offsetBlockHash"); HashDigest? offsetStateRootHash = context .GetArgument?>("offsetStateRootHash"); @@ -220,29 +177,13 @@ private static object ResolveBalance( "Either offsetBlockHash or offsetStateRootHash must be specified." ); case (blockhash: not null, _): - { - BlockHash offset; - try - { - offset = BlockHash.FromString(offsetBlockHash); - } - catch (Exception e) - { - throw new ExecutionError( - "offsetBlockHash must consist of hexadecimal digits.\n" + e.Message, - e - ); - } - - return context.Source.ChainStates.GetAccountState( - offset - ).GetValidatorSet().Validators; - } - + return context.Source.ChainStates + .GetAccountState(offsetBlockHash) + .GetValidatorSet().Validators; case (_, srh: not null): - return context.Source.ChainStates.GetAccountState( - offsetStateRootHash - ).GetValidatorSet().Validators; + return context.Source.ChainStates + .GetAccountState(offsetStateRootHash) + .GetValidatorSet().Validators; } } } diff --git a/Libplanet.Explorer/Queries/TransactionQuery.cs b/Libplanet.Explorer/Queries/TransactionQuery.cs index 6ef20f135a1..5d3c3abf166 100644 --- a/Libplanet.Explorer/Queries/TransactionQuery.cs +++ b/Libplanet.Explorer/Queries/TransactionQuery.cs @@ -111,13 +111,10 @@ public TransactionQuery(IBlockChainContext context) Field( "transaction", arguments: new QueryArguments( - new QueryArgument { Name = "id" } + new QueryArgument> { Name = "id" } ), resolve: context => - { - var id = TxId.FromHex(context.GetArgument("id")); - return ExplorerQuery.GetTransaction(id); - } + ExplorerQuery.GetTransaction(context.GetArgument("id")) ); Field>( @@ -212,7 +209,7 @@ public TransactionQuery(IBlockChainContext context) Field>( name: "transactionResult", arguments: new QueryArguments( - new QueryArgument> + new QueryArgument> { Name = "txId", Description = "transaction id.", @@ -223,7 +220,7 @@ public TransactionQuery(IBlockChainContext context) var blockChain = _context.BlockChain; var store = _context.Store; var index = _context.Index; - var txId = new TxId(ByteUtil.ParseHex(context.GetArgument("txId"))); + var txId = context.GetArgument("txId"); if (GetBlockContainingTx(_context, txId) is { } block) { From 72416498026170e4aa1c0d5e9ead8cc57e62dcfb Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Tue, 12 Dec 2023 22:16:37 +0900 Subject: [PATCH 2/5] Updated HashDigestType to be more generic --- .../GraphTypes/HashDigestSHA256Type.cs | 44 ------------- .../GraphTypes/HashDigestType.cs | 61 +++++++++++++++++++ Libplanet.Explorer/GraphTypes/TxResultType.cs | 5 +- Libplanet.Explorer/Queries/StateQuery.cs | 8 +-- 4 files changed, 68 insertions(+), 50 deletions(-) delete mode 100644 Libplanet.Explorer/GraphTypes/HashDigestSHA256Type.cs create mode 100644 Libplanet.Explorer/GraphTypes/HashDigestType.cs diff --git a/Libplanet.Explorer/GraphTypes/HashDigestSHA256Type.cs b/Libplanet.Explorer/GraphTypes/HashDigestSHA256Type.cs deleted file mode 100644 index 45d8f0ccf48..00000000000 --- a/Libplanet.Explorer/GraphTypes/HashDigestSHA256Type.cs +++ /dev/null @@ -1,44 +0,0 @@ -#nullable disable -using System; -using System.Security.Cryptography; -using GraphQL.Language.AST; -using GraphQL.Types; -using Libplanet.Common; - -namespace Libplanet.Explorer.GraphTypes -{ - public class HashDigestSHA256Type : StringGraphType - { - public HashDigestSHA256Type() - { - Name = "HashDigest_SHA256"; - } - - public override object Serialize(object value) - { - if (value is HashDigest hash) - { - return hash.ToString(); - } - - return value; - } - - public override object ParseValue(object value) => - value switch - { - null => null, - string hex => HashDigest.FromString(hex), - _ => throw new ArgumentException( - $"Expected a hexadecimal string but {value}", nameof(value)), - }; - - public override object ParseLiteral(IValue value) => - value switch - { - StringValue str => ParseValue(str.Value), - _ => throw new ArgumentException( - $"Expected a hexadecimal string but {value}", nameof(value)), - }; - } -} diff --git a/Libplanet.Explorer/GraphTypes/HashDigestType.cs b/Libplanet.Explorer/GraphTypes/HashDigestType.cs new file mode 100644 index 00000000000..4af971980bd --- /dev/null +++ b/Libplanet.Explorer/GraphTypes/HashDigestType.cs @@ -0,0 +1,61 @@ +using System.Security.Cryptography; +using GraphQL.Language.AST; +using GraphQL.Types; +using Libplanet.Common; + +namespace Libplanet.Explorer.GraphTypes +{ + public class HashDigestType : StringGraphType + where T : HashAlgorithm + { + public HashDigestType() + { + Name = $"HashDigest{typeof(T).Name}"; + } + + public override object? ParseLiteral(IValue value) + { + if (value is StringValue stringValue) + { + return ParseValue(stringValue.Value); + } + + if (value is NullValue) + { + return null; + } + + return ThrowLiteralConversionError(value); + } + + public override object? ParseValue(object? value) + { + if (value is null) + { + return null; + } + + if (value is string str) + { + return new HashDigest(ByteUtil.ParseHex(str)); + } + + return ThrowValueConversionError(value); + } + + public override object? Serialize(object? value) + { + if (value is null) + { + return null; + } + + if (value is HashDigest hashDigest) + { + return ByteUtil.Hex(hashDigest.ByteArray); + } + + return ThrowSerializationError(value); + } + } +} diff --git a/Libplanet.Explorer/GraphTypes/TxResultType.cs b/Libplanet.Explorer/GraphTypes/TxResultType.cs index 679c061d2dc..481bb4a4254 100644 --- a/Libplanet.Explorer/GraphTypes/TxResultType.cs +++ b/Libplanet.Explorer/GraphTypes/TxResultType.cs @@ -1,3 +1,4 @@ +using System.Security.Cryptography; using GraphQL.Types; namespace Libplanet.Explorer.GraphTypes @@ -24,14 +25,14 @@ public TxResultType() resolve: context => context.Source.BlockHash ); - Field( + Field>( nameof(TxResult.InputState), description: "The input state's root hash " + "which the target transaction executed.", resolve: context => context.Source.InputState ); - Field( + Field>( nameof(TxResult.OutputState), description: "The output state's root hash " + "which the target transaction executed.", diff --git a/Libplanet.Explorer/Queries/StateQuery.cs b/Libplanet.Explorer/Queries/StateQuery.cs index 1958129a4cd..3b93a873fdd 100644 --- a/Libplanet.Explorer/Queries/StateQuery.cs +++ b/Libplanet.Explorer/Queries/StateQuery.cs @@ -23,7 +23,7 @@ public StateQuery() new QueryArgument>>> { Name = "addresses" }, new QueryArgument { Name = "offsetBlockHash" }, - new QueryArgument { Name = "offsetStateRootHash" } + new QueryArgument> { Name = "offsetStateRootHash" } ), resolve: ResolveStates ); @@ -33,7 +33,7 @@ public StateQuery() new QueryArgument> { Name = "owner" }, new QueryArgument> { Name = "currency" }, new QueryArgument { Name = "offsetBlockHash" }, - new QueryArgument { Name = "offsetStateRootHash" } + new QueryArgument> { Name = "offsetStateRootHash" } ), resolve: ResolveBalance ); @@ -42,7 +42,7 @@ public StateQuery() arguments: new QueryArguments( new QueryArgument> { Name = "currency" }, new QueryArgument { Name = "offsetBlockHash" }, - new QueryArgument { Name = "offsetStateRootHash" } + new QueryArgument> { Name = "offsetStateRootHash" } ), resolve: ResolveTotalSupply ); @@ -50,7 +50,7 @@ public StateQuery() "validators", arguments: new QueryArguments( new QueryArgument { Name = "offsetBlockHash" }, - new QueryArgument { Name = "offsetStateRootHash" } + new QueryArgument> { Name = "offsetStateRootHash" } ), resolve: ResolveValidatorSet ); From 8087d2fed5175e370097d4484c46a7ec8e2bbbe2 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Tue, 12 Dec 2023 23:09:02 +0900 Subject: [PATCH 3/5] Updated tests --- .../GraphTypes/AddressTypeTest.cs | 61 ++++++++++++----- .../GraphTypes/BlockHashTypeTest.cs | 58 ++++++++++++++++ .../GraphTypes/HashDigestTypeTest.cs | 66 +++++++++++++++++++ .../GraphTypes/TxIdTypeTest.cs | 58 ++++++++++++++++ Libplanet.Explorer/GraphTypes/BlockType.cs | 9 +-- Libplanet.Explorer/GraphTypes/CurrencyType.cs | 5 +- 6 files changed, 233 insertions(+), 24 deletions(-) create mode 100644 Libplanet.Explorer.Tests/GraphTypes/BlockHashTypeTest.cs create mode 100644 Libplanet.Explorer.Tests/GraphTypes/HashDigestTypeTest.cs create mode 100644 Libplanet.Explorer.Tests/GraphTypes/TxIdTypeTest.cs diff --git a/Libplanet.Explorer.Tests/GraphTypes/AddressTypeTest.cs b/Libplanet.Explorer.Tests/GraphTypes/AddressTypeTest.cs index 7959417032d..14dd7e91e7d 100644 --- a/Libplanet.Explorer.Tests/GraphTypes/AddressTypeTest.cs +++ b/Libplanet.Explorer.Tests/GraphTypes/AddressTypeTest.cs @@ -1,4 +1,6 @@ using System; +using GraphQL.Language.AST; +using Libplanet.Common; using Libplanet.Crypto; using Libplanet.Explorer.GraphTypes; using Xunit; @@ -8,36 +10,59 @@ namespace Libplanet.Explorer.Tests.GraphTypes public class AddressTypeTest : ScalarGraphTypeTestBase { [Fact] - public void Serialize() + public void ParseLiteral() { - var randomBytes = TestUtils.GetRandomBytes(Address.Size); - var randomAddress = new Address(randomBytes); - object serialized = _type.Serialize(randomAddress); - Assert.Equal(randomAddress.ToString(), serialized); - } + Assert.Null(_type.ParseLiteral(new NullValue())); - [Theory] - [InlineData(null)] - [InlineData(0)] - [InlineData("")] - public void Serialize_ReturnsItselfIfNotAddressType(object value) - { - Assert.Equal(value, _type.Serialize(value)); + var bytes = TestUtils.GetRandomBytes(Address.Size); + var address = new Address(bytes); + var hex = ByteUtil.Hex(bytes); + var prefixedHex = address.ToString(); + Assert.Equal( + address, + Assert.IsType
(_type.ParseLiteral(new StringValue(prefixedHex)))); + Assert.Equal( + address, + Assert.IsType
(_type.ParseLiteral(new StringValue(hex)))); + + Assert.Throws( + () => _type.ParseLiteral(new LongValue(1234))); + Assert.Throws( + () => _type.ParseValue(new StringValue("address"))); } [Fact] public void ParseValue() { Assert.Null(_type.ParseValue(null)); - Assert.Equal(new Address(), _type.ParseValue("0x0000000000000000000000000000000000000000")); + + var bytes = TestUtils.GetRandomBytes(Address.Size); + var address = new Address(bytes); + var hex = ByteUtil.Hex(bytes); + var prefixedHex = address.ToString(); + Assert.Equal(address, _type.ParseValue(prefixedHex)); + Assert.Equal(address, _type.ParseValue(hex)); + + Assert.Throws(() => _type.ParseValue(0)); + Assert.Throws(() => _type.ParseValue(new Address())); + Assert.Throws(() => _type.ParseValue(new object())); } [Fact] - public void ParseValue_ThrowsArgumentException() + public void Serialize() { - Assert.Throws(() => _type.ParseValue(0)); - Assert.Throws(() => _type.ParseValue(new Address())); - Assert.Throws(() => _type.ParseValue(new object())); + Assert.Null(_type.Serialize(null)); + + var bytes = TestUtils.GetRandomBytes(Address.Size); + var address = new Address(bytes); + var hex = ByteUtil.Hex(bytes); + var prefixedHex = address.ToString(); + Assert.Equal(prefixedHex, _type.Serialize(address)); + Assert.NotEqual(hex, _type.Serialize(address)); + + Assert.Throws(() => _type.Serialize(0)); + Assert.Throws(() => _type.Serialize("")); + Assert.Throws(() => _type.Serialize(new object())); } } } diff --git a/Libplanet.Explorer.Tests/GraphTypes/BlockHashTypeTest.cs b/Libplanet.Explorer.Tests/GraphTypes/BlockHashTypeTest.cs new file mode 100644 index 00000000000..4c47649a2de --- /dev/null +++ b/Libplanet.Explorer.Tests/GraphTypes/BlockHashTypeTest.cs @@ -0,0 +1,58 @@ +using System; +using GraphQL.Language.AST; +using Libplanet.Common; +using Libplanet.Explorer.GraphTypes; +using Libplanet.Types.Blocks; +using Xunit; + +namespace Libplanet.Explorer.Tests.GraphTypes +{ + public class BlockHashTypeTest : ScalarGraphTypeTestBase + { + [Fact] + public void ParseLiteral() + { + Assert.Null(_type.ParseLiteral(new NullValue())); + + var bytes = TestUtils.GetRandomBytes(BlockHash.Size); + var blockHash = new BlockHash(bytes); + var hex = ByteUtil.Hex(bytes); + Assert.Equal( + blockHash, + Assert.IsType(_type.ParseLiteral(new StringValue(hex)))); + + Assert.Throws( + () => _type.ParseLiteral(new LongValue(1234))); + Assert.Throws( + () => _type.ParseValue(new StringValue("blockHash"))); + } + + [Fact] + public void ParseValue() + { + Assert.Null(_type.ParseValue(null)); + + var bytes = TestUtils.GetRandomBytes(BlockHash.Size); + var blockHash = new BlockHash(bytes); + var hex = ByteUtil.Hex(bytes); + Assert.Equal(blockHash, _type.ParseValue(hex)); + + Assert.Throws(() => _type.ParseValue(0)); + Assert.Throws(() => _type.ParseValue(new BlockHash())); + Assert.Throws(() => _type.ParseValue(new object())); + } + + [Fact] + public void Serialize() + { + var bytes = TestUtils.GetRandomBytes(BlockHash.Size); + var blockHash = new BlockHash(bytes); + var hex = ByteUtil.Hex(bytes); + Assert.Equal(hex, _type.Serialize(blockHash)); + + Assert.Throws(() => _type.Serialize(0)); + Assert.Throws(() => _type.Serialize("")); + Assert.Throws(() => _type.Serialize(new object())); + } + } +} diff --git a/Libplanet.Explorer.Tests/GraphTypes/HashDigestTypeTest.cs b/Libplanet.Explorer.Tests/GraphTypes/HashDigestTypeTest.cs new file mode 100644 index 00000000000..a8c2ec3a678 --- /dev/null +++ b/Libplanet.Explorer.Tests/GraphTypes/HashDigestTypeTest.cs @@ -0,0 +1,66 @@ +using System; +using System.Security.Cryptography; +using GraphQL.Language.AST; +using Libplanet.Common; +using Libplanet.Crypto; +using Libplanet.Explorer.GraphTypes; +using Xunit; + +namespace Libplanet.Explorer.Tests.GraphTypes +{ + public class HashDigestTypeTest : ScalarGraphTypeTestBase> + { + [Fact] + public void ParseLiteral() + { + Assert.Null(_type.ParseLiteral(new NullValue())); + + var bytes = TestUtils.GetRandomBytes(HashDigest.Size); + var hashDigestSHA256 = new HashDigest(bytes); + var hex = ByteUtil.Hex(bytes); + Assert.Equal( + hashDigestSHA256, + Assert.IsType>(_type.ParseLiteral(new StringValue(hex)))); + + bytes = TestUtils.GetRandomBytes(HashDigest.Size); + hex = ByteUtil.Hex(bytes); + Assert.Throws( + () => _type.ParseLiteral(new StringValue(hex))); + Assert.Throws( + () => _type.ParseLiteral(new LongValue(1234))); + Assert.Throws( + () => _type.ParseValue(new StringValue("hashDigest"))); + } + + [Fact] + public void ParseValue() + { + Assert.Null(_type.ParseValue(null)); + + var bytes = TestUtils.GetRandomBytes(HashDigest.Size); + var hashDigest = new HashDigest(bytes); + var hex = ByteUtil.Hex(bytes); + Assert.Equal(hashDigest, _type.ParseValue(hex)); + + Assert.Throws( + () => _type.ParseValue(0)); + Assert.Throws( + () => _type.ParseValue(new HashDigest())); + Assert.Throws( + () => _type.ParseValue(new object())); + } + + [Fact] + public void Serialize() + { + var bytes = TestUtils.GetRandomBytes(HashDigest.Size); + var hashDigest = new HashDigest(bytes); + var hex = ByteUtil.Hex(bytes); + Assert.Equal(hex, _type.Serialize(hashDigest)); + + Assert.Throws(() => _type.Serialize(0)); + Assert.Throws(() => _type.Serialize("")); + Assert.Throws(() => _type.Serialize(new object())); + } + } +} diff --git a/Libplanet.Explorer.Tests/GraphTypes/TxIdTypeTest.cs b/Libplanet.Explorer.Tests/GraphTypes/TxIdTypeTest.cs new file mode 100644 index 00000000000..569c9fbb92e --- /dev/null +++ b/Libplanet.Explorer.Tests/GraphTypes/TxIdTypeTest.cs @@ -0,0 +1,58 @@ +using System; +using GraphQL.Language.AST; +using Libplanet.Common; +using Libplanet.Explorer.GraphTypes; +using Libplanet.Types.Tx; +using Xunit; + +namespace Libplanet.Explorer.Tests.GraphTypes +{ + public class TxIdTypeTest : ScalarGraphTypeTestBase + { + [Fact] + public void ParseLiteral() + { + Assert.Null(_type.ParseLiteral(new NullValue())); + + var bytes = TestUtils.GetRandomBytes(TxId.Size); + var txId = new TxId(bytes); + var hex = ByteUtil.Hex(bytes); + Assert.Equal( + txId, + Assert.IsType(_type.ParseLiteral(new StringValue(hex)))); + + Assert.Throws( + () => _type.ParseLiteral(new LongValue(1234))); + Assert.Throws( + () => _type.ParseValue(new StringValue("txId"))); + } + + [Fact] + public void ParseValue() + { + Assert.Null(_type.ParseValue(null)); + + var bytes = TestUtils.GetRandomBytes(TxId.Size); + var txId = new TxId(bytes); + var hex = ByteUtil.Hex(bytes); + Assert.Equal(txId, _type.ParseValue(hex)); + + Assert.Throws(() => _type.ParseValue(0)); + Assert.Throws(() => _type.ParseValue(new TxId())); + Assert.Throws(() => _type.ParseValue(new object())); + } + + [Fact] + public void Serialize() + { + var bytes = TestUtils.GetRandomBytes(TxId.Size); + var txId = new TxId(bytes); + var hex = ByteUtil.Hex(bytes); + Assert.Equal(hex, _type.Serialize(txId)); + + Assert.Throws(() => _type.Serialize(0)); + Assert.Throws(() => _type.Serialize("")); + Assert.Throws(() => _type.Serialize(new object())); + } + } +} diff --git a/Libplanet.Explorer/GraphTypes/BlockType.cs b/Libplanet.Explorer/GraphTypes/BlockType.cs index 4b737df4de8..de4c7812c14 100644 --- a/Libplanet.Explorer/GraphTypes/BlockType.cs +++ b/Libplanet.Explorer/GraphTypes/BlockType.cs @@ -1,3 +1,4 @@ +using System.Security.Cryptography; using GraphQL.Types; using Libplanet.Explorer.Interfaces; using Libplanet.Types.Blocks; @@ -44,11 +45,11 @@ public BlockType(IBlockChainContext context) } ); Field(x => x.Timestamp); - Field>( + Field>>( name: "StateRootHash", description: "The hash of the resulting states after evaluating transactions " + "and a block action (if exists)", - resolve: ctx => ctx.Source.StateRootHash.ToByteArray()); + resolve: ctx => ctx.Source.StateRootHash); Field( name: "Signature", description: "The digital signature of the whole block content (except for hash, " + @@ -79,10 +80,10 @@ public BlockType(IBlockChainContext context) deprecationReason: "Block does not have Nonce field in PBFT.", resolve: _ => new byte[] { } ); - Field>( + Field>>( name: "PreEvaluationHash", description: "The hash of PreEvaluationBlock.", - resolve: ctx => ctx.Source.PreEvaluationHash.ToByteArray()); + resolve: ctx => ctx.Source.PreEvaluationHash); Name = "Block"; } diff --git a/Libplanet.Explorer/GraphTypes/CurrencyType.cs b/Libplanet.Explorer/GraphTypes/CurrencyType.cs index 5786c121ddd..8c8bcfa450f 100644 --- a/Libplanet.Explorer/GraphTypes/CurrencyType.cs +++ b/Libplanet.Explorer/GraphTypes/CurrencyType.cs @@ -1,4 +1,5 @@ using System.Linq; +using System.Security.Cryptography; using GraphQL.Types; using Libplanet.Types.Assets; @@ -39,10 +40,10 @@ public CurrencyType() "Whether the total supply of this currency is trackable.", resolve: context => context.Source.TotalSupplyTrackable ); - Field>( + Field>>( "hash", "The deterministic hash derived from other fields.", - resolve: context => context.Source.Hash.ToByteArray() + resolve: context => context.Source.Hash ); } } From 5e119e4c89abc2575593bef0141eab83e735888b Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Tue, 12 Dec 2023 23:19:39 +0900 Subject: [PATCH 4/5] Updated PublicKeyType --- .../GraphTypes/PublicKeyTypeTest.cs | 59 +++++++++++++------ .../GraphTypes/PublicKeyType.cs | 52 +++++++++------- 2 files changed, 73 insertions(+), 38 deletions(-) diff --git a/Libplanet.Explorer.Tests/GraphTypes/PublicKeyTypeTest.cs b/Libplanet.Explorer.Tests/GraphTypes/PublicKeyTypeTest.cs index 7818449dc83..0411e7fcc80 100644 --- a/Libplanet.Explorer.Tests/GraphTypes/PublicKeyTypeTest.cs +++ b/Libplanet.Explorer.Tests/GraphTypes/PublicKeyTypeTest.cs @@ -1,4 +1,5 @@ using System; +using GraphQL.Language.AST; using Libplanet.Crypto; using Libplanet.Explorer.GraphTypes; using Xunit; @@ -8,35 +9,57 @@ namespace Libplanet.Explorer.Tests.GraphTypes public class PublicKeyTypeTest : ScalarGraphTypeTestBase { [Fact] - public void Serialize() + public void ParseLiteral() { - var randomPublicKey = new PrivateKey().PublicKey; - object serialized = _type.Serialize(randomPublicKey); - Assert.Equal(randomPublicKey.ToString(), serialized); - } + Assert.Null(_type.ParseLiteral(new NullValue())); - [Theory] - [InlineData(null)] - [InlineData(0)] - [InlineData("")] - public void Serialize_ReturnsItselfIfNotPublicKeyType(object value) - { - Assert.Equal(value, _type.Serialize(value)); + var publicKey = new PrivateKey().PublicKey; + var compressed = publicKey.ToHex(true); + var uncompressed = publicKey.ToHex(false); + Assert.Equal( + publicKey, + Assert.IsType(_type.ParseLiteral(new StringValue(compressed)))); + Assert.Equal( + publicKey, + Assert.IsType(_type.ParseLiteral(new StringValue(uncompressed)))); + + Assert.Throws( + () => _type.ParseLiteral(new LongValue(1234))); + Assert.Throws( + () => _type.ParseValue(new StringValue("publicKey"))); } [Fact] public void ParseValue() { - var expected = new PrivateKey().PublicKey; - Assert.Equal(expected, _type.ParseValue(expected.ToString())); + Assert.Null(_type.ParseValue(null)); + + var publicKey = new PrivateKey().PublicKey; + var compressed = publicKey.ToHex(true); + var uncompressed = publicKey.ToHex(false); + Assert.Equal(publicKey, _type.ParseValue(compressed)); + Assert.Equal(publicKey, _type.ParseValue(uncompressed)); + + Assert.Throws(() => _type.ParseValue(0)); + Assert.Throws( + () => _type.ParseValue(new PrivateKey().PublicKey)); + Assert.Throws(() => _type.ParseValue(new object())); } [Fact] - public void ParseValue_ThrowsArgumentException() + public void Serialize() { - Assert.Throws(() => _type.ParseValue(0)); - Assert.Throws(() => _type.ParseValue(new PrivateKey().PublicKey)); - Assert.Throws(() => _type.ParseValue(new object())); + Assert.Null(_type.Serialize(null)); + + var publicKey = new PrivateKey().PublicKey; + var compressed = publicKey.ToHex(true); + var uncompressed = publicKey.ToHex(false); + Assert.Equal(compressed, _type.Serialize(publicKey)); + Assert.NotEqual(uncompressed, _type.Serialize(publicKey)); + + Assert.Throws(() => _type.Serialize(0)); + Assert.Throws(() => _type.Serialize("")); + Assert.Throws(() => _type.Serialize(new object())); } } } diff --git a/Libplanet.Explorer/GraphTypes/PublicKeyType.cs b/Libplanet.Explorer/GraphTypes/PublicKeyType.cs index 83ceeba7649..bce2b3762e4 100644 --- a/Libplanet.Explorer/GraphTypes/PublicKeyType.cs +++ b/Libplanet.Explorer/GraphTypes/PublicKeyType.cs @@ -1,7 +1,6 @@ -#nullable disable -using System; using GraphQL.Language.AST; using GraphQL.Types; +using Libplanet.Common; using Libplanet.Crypto; namespace Libplanet.Explorer.GraphTypes @@ -10,41 +9,54 @@ public class PublicKeyType : StringGraphType { public PublicKeyType() { - Name = "PublicKey"; + Name = "publicKey"; } - public override object Serialize(object value) + public override object? ParseLiteral(IValue value) { - if (value is PublicKey pubKey) + if (value is StringValue stringValue) { - return pubKey.ToHex(true); + return ParseValue(stringValue.Value); } - return value; + if (value is NullValue) + { + return null; + } + + return ThrowLiteralConversionError(value); } - public override object ParseValue(object value) + public override object? ParseValue(object? value) { - switch (value) + if (value is null) + { + return null; + } + + if (value is string str) { - case null: - return null; - case string hex: - return PublicKey.FromHex(hex); - default: - throw new ArgumentException( - $"Expected a hexadecimal string but {value}", nameof(value)); + // NOTE: Compressed and uncompressed should both be allowed. + return new PublicKey(ByteUtil.ParseHex(str)); } + + return ThrowValueConversionError(value); } - public override object ParseLiteral(IValue value) + public override object? Serialize(object? value) { - if (value is StringValue) + if (value is null) + { + return null; + } + + if (value is PublicKey publicKey) { - return ParseValue(value.Value); + // NOTE: Compressed format is preferred as output. + return publicKey.ToString(); } - return null; + return ThrowSerializationError(value); } } } From 65bb38b9639f64f14feed57d2591029fec102d2d Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Tue, 12 Dec 2023 23:22:30 +0900 Subject: [PATCH 5/5] Changelog --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 13b68de9e77..4077f2681bb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,12 @@ Version 3.9.2 To be released. + - (Libplanet.Explorer) Added `BlockHashType` and `TxIdType`. [[#3549]] + - (Libplanet.Explorer) Changed `HashDigestSHA256Type` to `HashDigestType`. + [[#3549]] + +[#3549]: https://github.com/planetarium/libplanet/pull/3549 + Version 3.9.1 -------------