Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix and improve evidence query #4014

Open
wants to merge 3 commits into
base: 5.4-maintenance
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,16 @@ Libplanet changelog
Version 5.4.3
-------------

To be released.
### Backward-incompatible API changes

- (Libplanet.Explorer) The parameter required by the `committedEvidence` query
was changed to hash or index. [[#4014]]
- (Libplanet.Explorer) The `desc`, `offset`, and `limit` parameters were
removed from the `committedEvidence` query. [[#4014]]
- (Libplanet.Explorer) Fixed an issue where an exception was thrown when
running evidence queries with id. [[#4014]]

[#4014]: https://github.com/planetarium/libplanet/pull/4014


Version 5.4.2
Expand Down
2 changes: 1 addition & 1 deletion src/Libplanet/Blockchain/Policies/BlockPolicy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ public BlockPolicy(
if (block.Evidence.Any(evidence => evidence.Height < evidenceExpirationHeight))
{
return new InvalidBlockEvidencePendingDurationException(
$"Block #{block.Index} {block.Hash} includes evidence" +
$"Block #{block.Index} {block.Hash} includes evidence " +
$"that is older than expiration height {evidenceExpirationHeight}");
}

Expand Down
37 changes: 30 additions & 7 deletions test/Libplanet.Explorer.Tests/GeneratedBlockChainFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using Libplanet.Types.Tx;
using Libplanet.Store;
using Libplanet.Store.Trie;
using Libplanet.Tests.Blockchain.Evidence;

namespace Libplanet.Explorer.Tests;

Expand All @@ -29,6 +30,8 @@ public class GeneratedBlockChainFixture

public int MaxTxCount { get; }

public int MaxEvidenceCount { get; }

public ImmutableDictionary<Address, ImmutableArray<Block>>
MinedBlocks { get; private set; }

Expand All @@ -43,7 +46,8 @@ public GeneratedBlockChainFixture(
int maxTxCount = 20,
int privateKeyCount = 10,
ImmutableArray<ImmutableArray<ImmutableArray<SimpleAction>>>?
txActionsForSuffixBlocks = null)
txActionsForSuffixBlocks = null,
int maxEvidenceCount = 2)
{
txActionsForSuffixBlocks ??=
ImmutableArray<ImmutableArray<ImmutableArray<SimpleAction>>>.Empty;
Expand All @@ -65,6 +69,7 @@ public GeneratedBlockChainFixture(
.ToImmutableDictionary(
key => key.Address,
key => ImmutableArray<Transaction>.Empty);
MaxEvidenceCount = maxEvidenceCount;

var privateKey = new PrivateKey();
var policy = new BlockPolicy(
Expand Down Expand Up @@ -105,22 +110,24 @@ public GeneratedBlockChainFixture(

while (Chain.Count < blockCount)
{
AddBlock(GetRandomTransactions());
AddBlock(GetRandomTransactions(), GetRandomEvidence(height: Chain.Count - 1));
}

if (txActionsForSuffixBlocks is { } txActionsForSuffixBlocksVal)
{
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<EvidenceBase>.Empty;
AddBlock(txs, evs);
}
}
}
Expand Down Expand Up @@ -159,6 +166,21 @@ private Transaction GetRandomTransaction(PrivateKey pk, long nonce)
gasLimit: null);
}

private ImmutableArray<EvidenceBase> GetRandomEvidence(long height)
{
return Enumerable
.Range(0, Random.Next(MaxEvidenceCount))
.Select<int, EvidenceBase>(_ =>
{
return new TestEvidence(
height: height,
validatorAddress: new PrivateKey().Address,
timestamp: DateTimeOffset.UtcNow);
})
.OrderBy(ev => ev.Id)
.ToImmutableArray();
}

private ImmutableArray<SimpleAction> GetRandomActions()
{
return Enumerable
Expand All @@ -167,7 +189,8 @@ private ImmutableArray<SimpleAction> GetRandomActions()
.ToImmutableArray();
}

private void AddBlock(ImmutableArray<Transaction> transactions)
private void AddBlock(
ImmutableArray<Transaction> transactions, ImmutableArray<EvidenceBase> evidence)
{
var proposer = PrivateKeys[Random.Next(PrivateKeys.Length)];
var block = Chain.EvaluateAndSign(
Expand All @@ -179,9 +202,9 @@ private void AddBlock(ImmutableArray<Transaction> transactions)
Chain.Tip.Hash,
BlockContent.DeriveTxHash(transactions),
Chain.Store.GetChainBlockCommit(Chain.Store.GetCanonicalChainId()!.Value),
evidenceHash: null),
evidenceHash: BlockContent.DeriveEvidenceHash(evidence)),
transactions,
evidence: Array.Empty<EvidenceBase>()).Propose(),
evidence: evidence).Propose(),
proposer);
Chain.Append(
block,
Expand Down
58 changes: 58 additions & 0 deletions test/Libplanet.Explorer.Tests/GraphTypes/EvidenceIdTypeTest.cs
Original file line number Diff line number Diff line change
@@ -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<EvidenceIdType>
{
[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<EvidenceId>(_type.ParseLiteral(new StringValue(hex))));

Assert.Throws<InvalidOperationException>(
() => _type.ParseLiteral(new LongValue(1234)));
Assert.Throws<InvalidOperationException>(
() => _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<InvalidOperationException>(() => _type.ParseValue(0));
Assert.Throws<InvalidOperationException>(() => _type.ParseValue(new EvidenceId()));
Assert.Throws<InvalidOperationException>(() => _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<InvalidOperationException>(() => _type.Serialize(0));
Assert.Throws<InvalidOperationException>(() => _type.Serialize(""));
Assert.Throws<InvalidOperationException>(() => _type.Serialize(new object()));
}
}
}
189 changes: 189 additions & 0 deletions test/Libplanet.Explorer.Tests/Queries/EvidenceQueryTest.cs
Original file line number Diff line number Diff line change
@@ -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<ExecutionNode>(result.Data);
var resultDict =
Assert.IsAssignableFrom<IDictionary<string, object>>(resultData.ToValue());
var resultEvidence = Assert.IsAssignableFrom<object[]>(resultDict["committedEvidence"]);

for (var i = 0; i < block.Evidence.Count; i++)
{
var evidence = block.Evidence[i];
var resultEvidenceDict
= Assert.IsAssignableFrom<IDictionary<string, object>>(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<ExecutionNode>(result.Data);
var resultDict =
Assert.IsAssignableFrom<IDictionary<string, object>>(resultData.ToValue());
var resultEvidence = Assert.IsAssignableFrom<object[]>(resultDict["committedEvidence"]);

for (var i = 0; i < block.Evidence.Count; i++)
{
var evidence = block.Evidence[i];
var resultEvidenceDict
= Assert.IsAssignableFrom<IDictionary<string, object>>(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<EvidenceBase>
{
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<ExecutionNode>(result.Data);
var resultDict =
Assert.IsAssignableFrom<IDictionary<string, object>>(resultData.ToValue());
var resultEvidence = Assert.IsAssignableFrom<object[]>(resultDict["pendingEvidence"]);

for (var i = 0; i < evidenceList.Count; i++)
{
var evidence = evidenceList[i];
var resultEvidenceDict
= Assert.IsAssignableFrom<IDictionary<string, object>>(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<ExecutionNode>(result.Data);
var resultDict =
Assert.IsAssignableFrom<IDictionary<string, object>>(resultData.ToValue());

var resultEvidenceDict
= Assert.IsAssignableFrom<IDictionary<string, object>>(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<Block> GetBlocks()
{
for (var i = 0; i < _fixture.Chain.Count; i++)
{
var block = _fixture.Chain[i];
if (block.Evidence.Count > 0)
{
yield return block;
}
}
}
}
Loading
Loading