diff --git a/test/Libplanet.Explorer.Tests/GeneratedBlockChainFixture.cs b/test/Libplanet.Explorer.Tests/GeneratedBlockChainFixture.cs index 79487a95b6d..319a73be956 100644 --- a/test/Libplanet.Explorer.Tests/GeneratedBlockChainFixture.cs +++ b/test/Libplanet.Explorer.Tests/GeneratedBlockChainFixture.cs @@ -16,6 +16,7 @@ using Libplanet.Types.Tx; using Libplanet.Store; using Libplanet.Store.Trie; +using Libplanet.Tests.Blockchain.Evidence; namespace Libplanet.Explorer.Tests; @@ -29,6 +30,8 @@ public class GeneratedBlockChainFixture public int MaxTxCount { get; } + public int MaxEvidenceCount { get; } + public ImmutableDictionary> MinedBlocks { get; private set; } @@ -43,7 +46,8 @@ public GeneratedBlockChainFixture( int maxTxCount = 20, int privateKeyCount = 10, ImmutableArray>>? - txActionsForSuffixBlocks = null) + txActionsForSuffixBlocks = null, + int maxEvidenceCount = 2) { txActionsForSuffixBlocks ??= ImmutableArray>>.Empty; @@ -65,6 +69,7 @@ public GeneratedBlockChainFixture( .ToImmutableDictionary( key => key.Address, key => ImmutableArray.Empty); + MaxEvidenceCount = maxEvidenceCount; var privateKey = new PrivateKey(); var policy = new BlockPolicy( @@ -105,7 +110,7 @@ public GeneratedBlockChainFixture( while (Chain.Count < blockCount) { - AddBlock(GetRandomTransactions()); + AddBlock(GetRandomTransactions(), GetRandomEvidence(height: Chain.Count - 1)); } if (txActionsForSuffixBlocks is { } txActionsForSuffixBlocksVal) @@ -113,14 +118,16 @@ public GeneratedBlockChainFixture( foreach (var actionsForTransactions in txActionsForSuffixBlocksVal) { var pk = PrivateKeys[Random.Next(PrivateKeys.Length)]; - AddBlock(actionsForTransactions + var txs = actionsForTransactions .Select(actions => Transaction.Create( nonce: Chain.GetNextTxNonce(pk.Address), privateKey: pk, genesisHash: Chain.Genesis.Hash, actions: actions.ToPlainValues())) - .ToImmutableArray()); + .ToImmutableArray(); + var evs = ImmutableArray.Empty; + AddBlock(txs, evs); } } } @@ -159,6 +166,21 @@ private Transaction GetRandomTransaction(PrivateKey pk, long nonce) gasLimit: null); } + private ImmutableArray GetRandomEvidence(long height) + { + return Enumerable + .Range(0, Random.Next(MaxEvidenceCount)) + .Select(_ => + { + return new TestEvidence( + height: height, + validatorAddress: new PrivateKey().Address, + timestamp: DateTimeOffset.UtcNow); + }) + .OrderBy(ev => ev.Id) + .ToImmutableArray(); + } + private ImmutableArray GetRandomActions() { return Enumerable @@ -167,7 +189,8 @@ private ImmutableArray GetRandomActions() .ToImmutableArray(); } - private void AddBlock(ImmutableArray transactions) + private void AddBlock( + ImmutableArray transactions, ImmutableArray evidence) { var proposer = PrivateKeys[Random.Next(PrivateKeys.Length)]; var block = Chain.EvaluateAndSign( @@ -179,9 +202,9 @@ private void AddBlock(ImmutableArray transactions) Chain.Tip.Hash, BlockContent.DeriveTxHash(transactions), Chain.Store.GetChainBlockCommit(Chain.Store.GetCanonicalChainId()!.Value), - evidenceHash: null), + evidenceHash: BlockContent.DeriveEvidenceHash(evidence)), transactions, - evidence: Array.Empty()).Propose(), + evidence: evidence).Propose(), proposer); Chain.Append( block, diff --git a/test/Libplanet.Explorer.Tests/GraphTypes/EvidenceIdTypeTest.cs b/test/Libplanet.Explorer.Tests/GraphTypes/EvidenceIdTypeTest.cs new file mode 100644 index 00000000000..5b6353bfc82 --- /dev/null +++ b/test/Libplanet.Explorer.Tests/GraphTypes/EvidenceIdTypeTest.cs @@ -0,0 +1,58 @@ +using System; +using GraphQL.Language.AST; +using Libplanet.Common; +using Libplanet.Explorer.GraphTypes; +using Libplanet.Types.Evidence; +using Xunit; + +namespace Libplanet.Explorer.Tests.GraphTypes +{ + public class EvidenceIdTypeTest : ScalarGraphTypeTestBase + { + [Fact] + public void ParseLiteral() + { + Assert.Null(_type.ParseLiteral(new NullValue())); + + var bytes = TestUtils.GetRandomBytes(EvidenceId.Size); + var evidenceId = new EvidenceId(bytes); + var hex = ByteUtil.Hex(bytes); + Assert.Equal( + evidenceId, + Assert.IsType(_type.ParseLiteral(new StringValue(hex)))); + + Assert.Throws( + () => _type.ParseLiteral(new LongValue(1234))); + Assert.Throws( + () => _type.ParseValue(new StringValue("evidenceId"))); + } + + [Fact] + public void ParseValue() + { + Assert.Null(_type.ParseValue(null)); + + var bytes = TestUtils.GetRandomBytes(EvidenceId.Size); + var evidenceId = new EvidenceId(bytes); + var hex = ByteUtil.Hex(bytes); + Assert.Equal(evidenceId, _type.ParseValue(hex)); + + Assert.Throws(() => _type.ParseValue(0)); + Assert.Throws(() => _type.ParseValue(new EvidenceId())); + Assert.Throws(() => _type.ParseValue(new object())); + } + + [Fact] + public void Serialize() + { + var bytes = TestUtils.GetRandomBytes(EvidenceId.Size); + var evidenceId = new EvidenceId(bytes); + var hex = ByteUtil.Hex(bytes); + Assert.Equal(hex, _type.Serialize(evidenceId)); + + Assert.Throws(() => _type.Serialize(0)); + Assert.Throws(() => _type.Serialize("")); + Assert.Throws(() => _type.Serialize(new object())); + } + } +} diff --git a/test/Libplanet.Explorer.Tests/Queries/EvidenceQueryTest.cs b/test/Libplanet.Explorer.Tests/Queries/EvidenceQueryTest.cs new file mode 100644 index 00000000000..a39628084cf --- /dev/null +++ b/test/Libplanet.Explorer.Tests/Queries/EvidenceQueryTest.cs @@ -0,0 +1,189 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using GraphQL.Execution; +using Libplanet.Explorer.Queries; +using Xunit; +using static Libplanet.Explorer.Tests.GraphQLTestUtils; +using Libplanet.Types.Blocks; +using System; +using Libplanet.Tests.Blockchain.Evidence; +using Libplanet.Crypto; +using Libplanet.Types.Evidence; + +namespace Libplanet.Explorer.Tests.Queries; + +public class EvidenceQueryTest +{ + private readonly GeneratedBlockChainFixture _fixture; + private readonly MockBlockChainContext _source; + private readonly EvidenceQuery _queryGraph; + + public EvidenceQueryTest() + { + _fixture = new GeneratedBlockChainFixture(seed: 0); + _source = new MockBlockChainContext(_fixture.Chain); + _queryGraph = new EvidenceQuery(); + var _ = new ExplorerQuery(_source); + } + + [Fact] + public async Task ExecuteCommittedEvidenceByHeightAsync() + { + var blocks = GetBlocks().ToArray(); + var block = blocks[Random.Shared.Next(blocks.Length)]; + + var result = await ExecuteQueryAsync(@$"{{ + committedEvidence( + index: {block.Index}) {{ + id + type + height + targetAddress + timestamp + }} + }}", _queryGraph, source: _source); + Assert.Null(result.Errors); + var resultData = Assert.IsAssignableFrom(result.Data); + var resultDict = + Assert.IsAssignableFrom>(resultData.ToValue()); + var resultEvidence = Assert.IsAssignableFrom(resultDict["committedEvidence"]); + + for (var i = 0; i < block.Evidence.Count; i++) + { + var evidence = block.Evidence[i]; + var resultEvidenceDict + = Assert.IsAssignableFrom>(resultEvidence[i]); + Assert.Equal(evidence.Id.ToString(), resultEvidenceDict["id"]); + Assert.Equal(evidence.GetType().FullName, resultEvidenceDict["type"]); + Assert.Equal(evidence.Height, resultEvidenceDict["height"]); + Assert.Equal(evidence.TargetAddress.ToString(), resultEvidenceDict["targetAddress"]); + Assert.Equal( + evidence.Timestamp, DateTimeOffset.Parse($"{resultEvidenceDict["timestamp"]}")); + } + } + + [Fact] + public async Task ExecuteCommittedEvidenceByHashAsync() + { + var blocks = GetBlocks().ToArray(); + var block = blocks[Random.Shared.Next(blocks.Length)]; + + var result = await ExecuteQueryAsync(@$"{{ + committedEvidence( + hash: ""{block.Hash}"") {{ + id + type + height + targetAddress + timestamp + }} + }}", _queryGraph, source: _source); + Assert.Null(result.Errors); + var resultData = Assert.IsAssignableFrom(result.Data); + var resultDict = + Assert.IsAssignableFrom>(resultData.ToValue()); + var resultEvidence = Assert.IsAssignableFrom(resultDict["committedEvidence"]); + + for (var i = 0; i < block.Evidence.Count; i++) + { + var evidence = block.Evidence[i]; + var resultEvidenceDict + = Assert.IsAssignableFrom>(resultEvidence[i]); + Assert.Equal(evidence.Id.ToString(), resultEvidenceDict["id"]); + Assert.Equal(evidence.GetType().FullName, resultEvidenceDict["type"]); + Assert.Equal(evidence.Height, resultEvidenceDict["height"]); + Assert.Equal(evidence.TargetAddress.ToString(), resultEvidenceDict["targetAddress"]); + Assert.Equal( + evidence.Timestamp, DateTimeOffset.Parse($"{resultEvidenceDict["timestamp"]}")); + } + } + + [Fact] + public async Task ExecutePendingEvidenceAsync() + { + var evidenceList = new List + { + new TestEvidence( + height: _fixture.Chain.Count - 3, + validatorAddress: new PrivateKey().Address, + timestamp: DateTimeOffset.UtcNow) + }; + + foreach (var evidence in evidenceList) + { + _fixture.Chain.AddEvidence(evidence); + } + + var result = await ExecuteQueryAsync(@$"{{ + pendingEvidence {{ + id + type + height + targetAddress + timestamp + }} + }}", _queryGraph, source: _source); + Assert.Null(result.Errors); + var resultData = Assert.IsAssignableFrom(result.Data); + var resultDict = + Assert.IsAssignableFrom>(resultData.ToValue()); + var resultEvidence = Assert.IsAssignableFrom(resultDict["pendingEvidence"]); + + for (var i = 0; i < evidenceList.Count; i++) + { + var evidence = evidenceList[i]; + var resultEvidenceDict + = Assert.IsAssignableFrom>(resultEvidence[i]); + Assert.Equal(evidence.Id.ToString(), resultEvidenceDict["id"]); + Assert.Equal(evidence.GetType().FullName, resultEvidenceDict["type"]); + Assert.Equal(evidence.Height, resultEvidenceDict["height"]); + Assert.Equal(evidence.TargetAddress.ToString(), resultEvidenceDict["targetAddress"]); + Assert.Equal( + evidence.Timestamp, DateTimeOffset.Parse($"{resultEvidenceDict["timestamp"]}")); + } + } + + [Fact] + public async Task ExecuteEvidenceByIdAsync() + { + var blocks = GetBlocks().ToArray(); + var block = blocks[Random.Shared.Next(blocks.Length)]; + var evidence = block.Evidence[Random.Shared.Next(block.Evidence.Count)]; + + var result = await ExecuteQueryAsync(@$"{{ + evidence(id: ""{evidence.Id}"") {{ + id + type + height + targetAddress + timestamp + }} + }}", _queryGraph, source: _source); + Assert.Null(result.Errors); + var resultData = Assert.IsAssignableFrom(result.Data); + var resultDict = + Assert.IsAssignableFrom>(resultData.ToValue()); + + var resultEvidenceDict + = Assert.IsAssignableFrom>(resultDict["evidence"]); + Assert.Equal(evidence.Id.ToString(), resultEvidenceDict["id"]); + Assert.Equal(evidence.GetType().FullName, resultEvidenceDict["type"]); + Assert.Equal(evidence.Height, resultEvidenceDict["height"]); + Assert.Equal(evidence.TargetAddress.ToString(), resultEvidenceDict["targetAddress"]); + Assert.Equal( + evidence.Timestamp, DateTimeOffset.Parse($"{resultEvidenceDict["timestamp"]}")); + } + + private IEnumerable GetBlocks() + { + for (var i = 0; i < _fixture.Chain.Count; i++) + { + var block = _fixture.Chain[i]; + if (block.Evidence.Count > 0) + { + yield return block; + } + } + } +}