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;
+ }
+ }
+ }
+}