Skip to content

Commit

Permalink
wip: evidence
Browse files Browse the repository at this point in the history
  • Loading branch information
s2quake committed Dec 10, 2024
1 parent 842da15 commit 06aafad
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 43 deletions.
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()));
}
}
}
97 changes: 97 additions & 0 deletions test/Libplanet.Explorer.Tests/Queries/EvidenceQueryTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Bencodex.Types;
using GraphQL;
using GraphQL.Execution;
using GraphQL.Types;
using Libplanet.Action;
using Libplanet.Action.Sys;
using Libplanet.Blockchain;
using Libplanet.Blockchain.Policies;
using Libplanet.Common;
using Libplanet.Crypto;
using Libplanet.Types.Assets;
using Libplanet.Types.Consensus;
using Libplanet.Types.Tx;
using Libplanet.Explorer.Queries;
using Libplanet.Store;
using Libplanet.Store.Trie;
using Xunit;
using static Libplanet.Explorer.Tests.GraphQLTestUtils;
using Libplanet.Action.Loader;
using Libplanet.Types.Evidence;
using Libplanet.Types.Blocks;
using System.Globalization;

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 ExecuteAsync()
{
var blocks = GetBlocks().ToArray();
var block = blocks[System.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 evidenceData = (object[])resultDict["committedEvidence"];
Assert.Equal(block.Evidence.Count, evidenceData.Length);

for (var i = 0; i < block.Evidence.Count; i++)
{
var evidence = block.Evidence[i];
var data = (IDictionary<string, object>)evidenceData[i];
Assert.Equal(evidence.Id.ToString(), data["id"]);
Assert.Equal(evidence.GetType().FullName, data["type"]);
Assert.Equal(evidence.Height, data["height"]);
Assert.Equal(evidence.TargetAddress.ToString(), data["targetAddress"]);
Assert.Equal(
new DateTimeOffsetGraphType().Serialize(evidence.Timestamp),
data["timestamp"]);
// Assert.Equal(block.Evidence[i].Id.ToString(), ((IList<IDictionary<string, object>>)resultDict["committedEvidence"])[i]["id"]);
}
}

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;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using Xunit;
using static Libplanet.Explorer.Tests.GraphQLTestUtils;
using Libplanet.Action.Loader;
using Libplanet.Types.Evidence;

namespace Libplanet.Explorer.Tests.Queries;

Expand Down
48 changes: 22 additions & 26 deletions tools/Libplanet.Explorer/Queries/EvidenceQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,32 @@ public EvidenceQuery()
Field<NonNullGraphType<ListGraphType<NonNullGraphType<EvidenceType>>>>(
"committedEvidence",
arguments: new QueryArguments(
new QueryArgument<BlockHashType>
{
Name = "blockHash",
DefaultValue = null,
},
new QueryArgument<BooleanGraphType>
new QueryArgument<IdGraphType> { Name = "hash" },
new QueryArgument<IdGraphType> { Name = "index" }
),
resolve: context =>
{
string hash = context.GetArgument<string>("hash");
long? index = context.GetArgument<long?>("index", null);

if (!(hash is null ^ index is null))
{
Name = "desc",
DefaultValue = false,
},
new QueryArgument<IntGraphType>
throw new ExecutionError(
"The parameters hash and index are mutually exclusive; " +
"give only one at a time.");
}

if (hash is { } nonNullHash)
{
Name = "offset",
DefaultValue = 0,
},
new QueryArgument<IntGraphType>
return ExplorerQuery.ListCommitEvidence(BlockHash.FromString(nonNullHash));
}

if (index is { } nonNullIndex)
{
Name = "limit",
DefaultValue = MaxLimit,
return ExplorerQuery.ListCommitEvidence(nonNullIndex);
}
),
resolve: context =>
{
var blockHash = context.GetArgument<BlockHash?>("blockHash");
bool desc = context.GetArgument<bool>("desc");
int offset = context.GetArgument<int>("offset");
int? limit = context.GetArgument<int?>("limit");

return ExplorerQuery.ListCommitEvidence(blockHash, desc, offset, limit);
throw new ExecutionError("Unexpected block query");
}
);

Expand Down Expand Up @@ -87,8 +84,7 @@ public EvidenceQuery()
new QueryArgument<EvidenceIdType> { Name = "id" }
),
resolve: context => ExplorerQuery.GetEvidence(
new EvidenceId(ByteUtil.ParseHex(context.GetArgument<string>("id")
?? throw new ExecutionError("Given id cannot be null."))))
context.GetArgument<EvidenceId>("id"))
);
}
}
Expand Down
18 changes: 9 additions & 9 deletions tools/Libplanet.Explorer/Queries/ExplorerQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,18 +131,18 @@ internal static IEnumerable<EvidenceBase> ListPendingEvidence(
return evidence;
}

internal static IEnumerable<EvidenceBase> ListCommitEvidence(
BlockHash? blockHash, bool desc, int offset, int? limit)
internal static IEnumerable<EvidenceBase> ListCommitEvidence(BlockHash blockHash)
{
var blockChain = Chain;
var block = blockHash != null ? blockChain[blockHash.Value] : blockChain.Tip;
var comparer = desc ? EvidenceIdComparer.Descending : EvidenceIdComparer.Ascending;
var evidence = block.Evidence
.Skip(offset)
.Take(limit ?? int.MaxValue)
.OrderBy(ev => ev.Id, comparer);
var block = blockChain[blockHash];
return block.Evidence;
}

return evidence;
internal static IEnumerable<EvidenceBase> ListCommitEvidence(long index)
{
var blockChain = Chain;
var block = blockChain[index];
return block.Evidence;
}

internal static Block? GetBlockByHash(BlockHash hash) => Store.GetBlock(hash);
Expand Down

0 comments on commit 06aafad

Please sign in to comment.