Skip to content

Commit

Permalink
Merge pull request #1030 from moreal/feature/mpt-debugger
Browse files Browse the repository at this point in the history
feat(mpt): provide API to calculate state root hash in the action execution
  • Loading branch information
moreal committed Oct 15, 2020
2 parents a802cb2 + 6442836 commit aaa5bba
Show file tree
Hide file tree
Showing 20 changed files with 219 additions and 91 deletions.
3 changes: 3 additions & 0 deletions CHANGES.md
Expand Up @@ -226,6 +226,8 @@ To be released.
- Added `Block<T>.StateRootHash` property. [[#986]]
- Added `BlockHeader.StateRootHash` property. [[#986]]
- Added `MerkleTrieExtensions` static class. [[#1023]]
- Added `IAccountStateDelta.PreviousStateRootHash` property to
calculate states until previous action as state root hash. [[#1030]]

### Behavioral changes

Expand Down Expand Up @@ -378,6 +380,7 @@ To be released.
[#1022]: https://github.com/planetarium/libplanet/pull/1022
[#1023]: https://github.com/planetarium/libplanet/pull/1023
[#1026]: https://github.com/planetarium/libplanet/pull/1026
[#1030]: https://github.com/planetarium/libplanet/pull/1030
[sleep mode]: https://en.wikipedia.org/wiki/Sleep_mode


Expand Down
35 changes: 0 additions & 35 deletions Libplanet.Tests/Action/AccountStateDeltaExtensions.cs

This file was deleted.

29 changes: 29 additions & 0 deletions Libplanet.Tests/Action/ActionContextTest.cs
@@ -1,8 +1,11 @@
using System;
using System.Collections.Immutable;
using System.Linq;
using Bencodex.Types;
using Libplanet.Action;
using Libplanet.Assets;
using Libplanet.Store.Trie;
using Libplanet.Tests.Store.Trie;
using Xunit;

namespace Libplanet.Tests.Action
Expand Down Expand Up @@ -128,6 +131,32 @@ public void GetUnconsumedContext()
);
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void LazyPreviousStateRootHash(bool callPreviousStateRootHash)
{
var keyValueStore = new MemoryKeyValueStore();
ITrie previousBlockStatesTrie = new MerkleTrie(keyValueStore);
previousBlockStatesTrie = previousBlockStatesTrie.Set(new byte[0], default(Null));
var random = new System.Random();
var actionContext = new ActionContext(
signer: random.NextAddress(),
miner: random.NextAddress(),
blockIndex: 1,
previousStates: new DumbAccountStateDelta(),
randomSeed: random.Next(),
previousBlockStatesTrie: previousBlockStatesTrie
);

if (callPreviousStateRootHash)
{
_ = actionContext.PreviousStateRootHash;
}

Assert.Equal(callPreviousStateRootHash ? 1 : 0, keyValueStore.ListKeys().Count());
}

private class DumbAccountStateDelta : IAccountStateDelta
{
public IImmutableSet<Address> UpdatedAddresses =>
Expand Down
2 changes: 1 addition & 1 deletion Libplanet.Tests/Blockchain/BlockEvaluatorTest.cs
Expand Up @@ -40,7 +40,7 @@ public void Idempotent()
transactions: txs,
checkStateRootHash: true);
var blockEvaluator =
new BlockEvaluator<RandomAction>(null, NullStateGetter, NullBalanceGetter);
new BlockEvaluator<RandomAction>(null, NullStateGetter, NullBalanceGetter, null);
var generatedRandomNumbers = new List<int>();

Assert.NotEqual(stateRootBlock.Hash, noStateRootBlock.Hash);
Expand Down
26 changes: 13 additions & 13 deletions Libplanet.Tests/Store/Trie/MerkleTrieExtensionsTest.cs
Expand Up @@ -17,14 +17,14 @@ public void DifferentNodes()
MerkleTrie trieA = new MerkleTrie(keyValueStore),
trieB = new MerkleTrie(keyValueStore);

trieA.Set(new byte[] { 0x01, }, default(Null));
trieA.Set(new byte[] { 0x02, }, default(Null));
trieA.Set(new byte[] { 0x03, }, default(Null));
trieB.Set(new byte[] { 0x01, }, Dictionary.Empty);
trieB.Set(new byte[] { 0x02, }, default(Null));
trieB.Set(new byte[] { 0x04, }, default(Null));
trieA = (MerkleTrie)trieA.Commit();
trieB = (MerkleTrie)trieB.Commit();
trieA = (MerkleTrie)trieA.Set(new byte[] { 0x01, }, default(Null))
.Set(new byte[] { 0x02, }, default(Null))
.Set(new byte[] { 0x03, }, default(Null))
.Commit();
trieB = (MerkleTrie)trieB.Set(new byte[] { 0x01, }, Dictionary.Empty)
.Set(new byte[] { 0x02, }, default(Null))
.Set(new byte[] { 0x04, }, default(Null))
.Commit();

Dictionary<string, (HashDigest<SHA256> Root, IValue Value)[]> differentNodes =
trieA.DifferentNodes(trieB).ToDictionary(
Expand All @@ -47,11 +47,11 @@ public void ListAllStates()
IKeyValueStore keyValueStore = new MemoryKeyValueStore();
MerkleTrie trie = new MerkleTrie(keyValueStore);

trie.Set(new byte[] { 0x01, }, default(Null));
trie.Set(new byte[] { 0x02, }, default(Null));
trie.Set(new byte[] { 0x03, }, default(Null));
trie.Set(new byte[] { 0x04, }, default(Null));
trie.Set(new byte[] { 0xbe, 0xef }, Dictionary.Empty);
trie = (MerkleTrie)trie.Set(new byte[] { 0x01, }, default(Null))
.Set(new byte[] { 0x02, }, default(Null))
.Set(new byte[] { 0x03, }, default(Null))
.Set(new byte[] { 0x04, }, default(Null))
.Set(new byte[] { 0xbe, 0xef }, Dictionary.Empty);

Dictionary<ImmutableArray<byte>, IValue> states =
trie.ListAllStates().ToDictionary(
Expand Down
4 changes: 2 additions & 2 deletions Libplanet.Tests/Store/Trie/MerkleTrieTest.cs
Expand Up @@ -35,13 +35,13 @@ public void IterateNodes()
// There is nothing.
Assert.Empty(merkleTrie.IterateNodes());

merkleTrie.Set(
merkleTrie = (MerkleTrie)merkleTrie.Set(
new byte[] { 0xbe, 0xef, },
Dictionary.Empty.Add(TestUtils.GetRandomBytes(32), default(Null)));
// There are (ShortNode, ValueNode)
Assert.Equal(2, merkleTrie.IterateNodes().Count());

merkleTrie = merkleTrie.Commit() as MerkleTrie;
merkleTrie = (MerkleTrie)merkleTrie.Commit();
// There are (HashNode, ShortNode, HashNode, ValueNode)
Assert.Equal(4, merkleTrie.IterateNodes().Count());
}
Expand Down
12 changes: 6 additions & 6 deletions Libplanet.Tests/Store/Trie/TrieTest.cs
Expand Up @@ -45,7 +45,7 @@ void CheckAddressStates()
foreach (var address in addresses)
{
states[address] = (Text)address.ToHex();
trie.Set(address.ToByteArray(), states[address]);
trie = trie.Set(address.ToByteArray(), states[address]);
CheckAddressStates();
}
}
Expand All @@ -71,11 +71,11 @@ public void Commit(int addressCount)
addresses[i] = new PrivateKey().ToAddress();
states[i] = (Binary)TestUtils.GetRandomBytes(128);

trieA.Set(addresses[i].ToByteArray(), states[i]);
trieA = trieA.Set(addresses[i].ToByteArray(), states[i]);
}

byte[] path = TestUtils.GetRandomBytes(32);
trieA.Set(path, (Text)"foo");
trieA = trieA.Set(path, (Text)"foo");
HashDigest<SHA256> rootHash = trieA.Hash;
Assert.True(trieA.TryGet(path, out IValue stateA));
Assert.Equal((Text)"foo", stateA);
Expand All @@ -85,7 +85,7 @@ public void Commit(int addressCount)
Assert.True(trieB.TryGet(path, out IValue stateB));
Assert.Equal((Text)"foo", stateB);

trieB.Set(path, (Text)"bar");
trieB = trieB.Set(path, (Text)"bar");

Assert.True(trieA.TryGet(path, out stateA));
Assert.Equal((Text)"foo", stateA);
Expand All @@ -111,7 +111,7 @@ public void EmptyRootHash()
var committedTrie = trie.Commit();
Assert.Equal(MerkleTrie.EmptyRootHash, committedTrie.Hash);

trie.Set(default(Address).ToByteArray(), Dictionary.Empty);
trie = trie.Set(default(Address).ToByteArray(), Dictionary.Empty);
committedTrie = trie.Commit();
Assert.NotEqual(MerkleTrie.EmptyRootHash, committedTrie.Hash);
}
Expand All @@ -124,7 +124,7 @@ public void ThrowArgumentNullExceptionWhenSettingNull()

Assert.Throws<ArgumentNullException>(() =>
{
trie.Set(new byte[] { 0xbe, 0xef }, null);
_ = trie.Set(new byte[] { 0xbe, 0xef }, null);
});
}
}
Expand Down
10 changes: 6 additions & 4 deletions Libplanet.Tests/TestUtils.cs
Expand Up @@ -18,6 +18,7 @@
using Libplanet.Tests.Common;
using Libplanet.Tx;
using Xunit;
using static Libplanet.Blockchain.KeyConverters;
using Random = System.Random;

namespace Libplanet.Tests
Expand Down Expand Up @@ -205,14 +206,15 @@ public static byte[] GetRandomBytes(int size)
var blockEvaluator = new BlockEvaluator<T>(
blockAction,
(address, digest, arg3) => null,
(address, currency, arg3, arg4) => new FungibleAssetValue(currency));
(address, currency, arg3, arg4) => new FungibleAssetValue(currency),
null);
var actionEvaluationResult = blockEvaluator
.EvaluateActions(block, StateCompleterSet<T>.Reject)
.GetTotalDelta(BlockChain<T>.ToStateKey, BlockChain<T>.ToFungibleAssetKey);
var trie = new MerkleTrie(new DefaultKeyValueStore(null));
.GetTotalDelta(ToStateKey, ToFungibleAssetKey);
ITrie trie = new MerkleTrie(new DefaultKeyValueStore(null));
foreach (var pair in actionEvaluationResult)
{
trie.Set(Encoding.UTF8.GetBytes(pair.Key), pair.Value);
trie = trie.Set(Encoding.UTF8.GetBytes(pair.Key), pair.Value);
}

var stateRootHash = trie.Commit(rehearsal: true).Hash;
Expand Down
1 change: 0 additions & 1 deletion Libplanet.Tests/Tx/TransactionTest.cs
Expand Up @@ -9,7 +9,6 @@
using Libplanet.Action;
using Libplanet.Assets;
using Libplanet.Crypto;
using Libplanet.Tests.Action;
using Libplanet.Tests.Common.Action;
using Libplanet.Tx;
using Xunit;
Expand Down
30 changes: 28 additions & 2 deletions Libplanet/Action/ActionContext.cs
@@ -1,18 +1,25 @@
#nullable enable
using System.Diagnostics.Contracts;
using System.Security.Cryptography;
using Libplanet.Store.Trie;

namespace Libplanet.Action
{
internal class ActionContext : IActionContext
{
private readonly int _randomSeed;
private readonly ITrie? _previousBlockStatesTrie;

private HashDigest<SHA256>? _previousStateRootHash;

public ActionContext(
Address signer,
Address miner,
long blockIndex,
IAccountStateDelta previousStates,
int randomSeed,
bool rehearsal = false
bool rehearsal = false,
ITrie? previousBlockStatesTrie = null
)
{
Signer = signer;
Expand All @@ -22,6 +29,7 @@ internal class ActionContext : IActionContext
PreviousStates = previousStates;
Random = new Random(randomSeed);
_randomSeed = randomSeed;
_previousBlockStatesTrie = previousBlockStatesTrie;
}

public Address Signer { get; }
Expand All @@ -36,8 +44,26 @@ internal class ActionContext : IActionContext

public IRandom Random { get; }

public HashDigest<SHA256>? PreviousStateRootHash
{
get
{
return _previousStateRootHash ??= _previousBlockStatesTrie?
.Set(PreviousStates.GetUpdatedRawStates())
.Commit()
.Hash;
}
}

[Pure]
public IActionContext GetUnconsumedContext() =>
new ActionContext(Signer, Miner, BlockIndex, PreviousStates, _randomSeed, Rehearsal);
new ActionContext(
Signer,
Miner,
BlockIndex,
PreviousStates,
_randomSeed,
Rehearsal,
_previousBlockStatesTrie);
}
}
9 changes: 7 additions & 2 deletions Libplanet/Action/ActionEvaluation.cs
Expand Up @@ -5,6 +5,7 @@
using System.Security.Cryptography;
using Libplanet.Blockchain.Policies;
using Libplanet.Blocks;
using Libplanet.Store.Trie;
using Libplanet.Tx;

namespace Libplanet.Action
Expand Down Expand Up @@ -84,6 +85,8 @@ public class ActionEvaluation
/// <param name="rehearsal">Pass <c>true</c> if it is intended
/// to be dry-run (i.e., the returned result will be never used).
/// The default value is <c>false</c>.</param>
/// <param name="previousBlockStatesTrie">The trie to contain states at previous block.
/// </param>
/// <returns>Enumerates <see cref="ActionEvaluation"/>s for each one in
/// <paramref name="actions"/>. The order is the same to the <paramref name="actions"/>.
/// Note that each <see cref="IActionContext.Random"/> object
Expand All @@ -98,7 +101,8 @@ public class ActionEvaluation
Address signer,
byte[] signature,
IImmutableList<IAction> actions,
bool rehearsal = false)
bool rehearsal = false,
ITrie previousBlockStatesTrie = null)
{
ActionContext CreateActionContext(
IAccountStateDelta prevStates,
Expand All @@ -110,7 +114,8 @@ int randomSeed
blockIndex: blockIndex,
previousStates: prevStates,
randomSeed: randomSeed,
rehearsal: rehearsal
rehearsal: rehearsal,
previousBlockStatesTrie: previousBlockStatesTrie
);

byte[] hashedSignature;
Expand Down
49 changes: 49 additions & 0 deletions Libplanet/Action/IAccountStateDeltaExtensions.cs
@@ -0,0 +1,49 @@
#nullable enable
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Bencodex.Types;
using Libplanet.Assets;
using static Libplanet.Blockchain.KeyConverters;

namespace Libplanet.Action
{
internal static class IAccountStateDeltaExtensions
{
internal static IImmutableDictionary<Address, IValue> GetUpdatedStates(
this IAccountStateDelta delta
)
{
return delta.StateUpdatedAddresses.Select(address =>
new KeyValuePair<Address, IValue>(
address,
delta.GetState(address)!
)
).ToImmutableDictionary();
}

internal static IImmutableDictionary<(Address, Currency), FungibleAssetValue>
GetUpdatedBalances(this IAccountStateDelta delta) =>
delta.UpdatedFungibleAssets.SelectMany(kv =>
kv.Value.Select(currency =>
new KeyValuePair<(Address, Currency), FungibleAssetValue>(
(kv.Key, currency),
delta.GetBalance(kv.Key, currency)
)
)
).ToImmutableDictionary();

internal static IImmutableDictionary<string, IValue> GetUpdatedRawStates(
this IAccountStateDelta delta) =>
delta.GetUpdatedStates()
.Select(pair =>
new KeyValuePair<string, IValue>(
ToStateKey(pair.Key),
pair.Value))
.Union(
delta.GetUpdatedBalances().Select(pair =>
new KeyValuePair<string, IValue>(
ToFungibleAssetKey(pair.Key),
(Integer)pair.Value.RawValue))).ToImmutableDictionary();
}
}

0 comments on commit aaa5bba

Please sign in to comment.