From 405c630827fca5d1bfff98f90112ddb3cd8ec605 Mon Sep 17 00:00:00 2001 From: moreal Date: Tue, 13 Oct 2020 19:03:11 +0900 Subject: [PATCH 1/7] refactor: move AccountStateDeltaExtensions --- Libplanet.Tests/Tx/TransactionTest.cs | 1 - .../Action/IAccountStateDeltaExtensions.cs | 14 +++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) rename Libplanet.Tests/Action/AccountStateDeltaExtensions.cs => Libplanet/Action/IAccountStateDeltaExtensions.cs (67%) diff --git a/Libplanet.Tests/Tx/TransactionTest.cs b/Libplanet.Tests/Tx/TransactionTest.cs index 55de574e1ad..2dcb003a651 100644 --- a/Libplanet.Tests/Tx/TransactionTest.cs +++ b/Libplanet.Tests/Tx/TransactionTest.cs @@ -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; diff --git a/Libplanet.Tests/Action/AccountStateDeltaExtensions.cs b/Libplanet/Action/IAccountStateDeltaExtensions.cs similarity index 67% rename from Libplanet.Tests/Action/AccountStateDeltaExtensions.cs rename to Libplanet/Action/IAccountStateDeltaExtensions.cs index 76f1b327599..9d3e53df879 100644 --- a/Libplanet.Tests/Action/AccountStateDeltaExtensions.cs +++ b/Libplanet/Action/IAccountStateDeltaExtensions.cs @@ -1,28 +1,28 @@ +#nullable enable using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using Bencodex.Types; -using Libplanet.Action; using Libplanet.Assets; -namespace Libplanet.Tests.Action +namespace Libplanet.Action { - public static class AccountStateDeltaExtensions + internal static class IAccountStateDeltaExtensions { - public static IImmutableDictionary GetUpdatedStates( + internal static IImmutableDictionary GetUpdatedStates( this IAccountStateDelta delta ) { return delta.StateUpdatedAddresses.Select(address => new KeyValuePair( address, - delta.GetState(address) + delta.GetState(address)! ) ).ToImmutableDictionary(); } - public static IImmutableDictionary<(Address, Currency), FungibleAssetValue> - GetUpdatedBalances(this IAccountStateDelta delta) => + internal static IImmutableDictionary<(Address, Currency), FungibleAssetValue> + GetUpdatedBalances(this IAccountStateDelta delta) => delta.UpdatedFungibleAssets.SelectMany(kv => kv.Value.Select(currency => new KeyValuePair<(Address, Currency), FungibleAssetValue>( From 6f0bc0ac494a192e1af03cbc6d01496c1dc33fd0 Mon Sep 17 00:00:00 2001 From: moreal Date: Tue, 13 Oct 2020 19:07:43 +0900 Subject: [PATCH 2/7] refactor: separate raw state key converters --- Libplanet.Tests/TestUtils.cs | 3 ++- Libplanet/Blockchain/BlockChain.cs | 10 +--------- Libplanet/Blockchain/KeyConverters.cs | 17 +++++++++++++++++ 3 files changed, 20 insertions(+), 10 deletions(-) create mode 100644 Libplanet/Blockchain/KeyConverters.cs diff --git a/Libplanet.Tests/TestUtils.cs b/Libplanet.Tests/TestUtils.cs index 9b9ed422e0e..8600caf5f43 100644 --- a/Libplanet.Tests/TestUtils.cs +++ b/Libplanet.Tests/TestUtils.cs @@ -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 @@ -208,7 +209,7 @@ public static byte[] GetRandomBytes(int size) (address, currency, arg3, arg4) => new FungibleAssetValue(currency)); var actionEvaluationResult = blockEvaluator .EvaluateActions(block, StateCompleterSet.Reject) - .GetTotalDelta(BlockChain.ToStateKey, BlockChain.ToFungibleAssetKey); + .GetTotalDelta(ToStateKey, ToFungibleAssetKey); var trie = new MerkleTrie(new DefaultKeyValueStore(null)); foreach (var pair in actionEvaluationResult) { diff --git a/Libplanet/Blockchain/BlockChain.cs b/Libplanet/Blockchain/BlockChain.cs index 738d8777249..17850f972fd 100644 --- a/Libplanet/Blockchain/BlockChain.cs +++ b/Libplanet/Blockchain/BlockChain.cs @@ -19,6 +19,7 @@ using Libplanet.Store.Trie; using Libplanet.Tx; using Serilog; +using static Libplanet.Blockchain.KeyConverters; namespace Libplanet.Blockchain { @@ -826,15 +827,6 @@ public long GetNextTxNonce(Address address) } } - internal static string ToStateKey(Address address) => address.ToHex().ToLowerInvariant(); - - internal static string ToFungibleAssetKey(Address address, Currency currency) => - "_" + address.ToHex().ToLowerInvariant() + - "_" + ByteUtil.Hex(currency.Hash.ByteArray).ToLowerInvariant(); - - internal static string ToFungibleAssetKey((Address, Currency) pair) => - ToFungibleAssetKey(pair.Item1, pair.Item2); - internal void Append( Block block, DateTimeOffset currentTime, diff --git a/Libplanet/Blockchain/KeyConverters.cs b/Libplanet/Blockchain/KeyConverters.cs new file mode 100644 index 00000000000..8a7ea2a1942 --- /dev/null +++ b/Libplanet/Blockchain/KeyConverters.cs @@ -0,0 +1,17 @@ +#nullable enable +using Libplanet.Assets; + +namespace Libplanet.Blockchain +{ + internal static class KeyConverters + { + internal static string ToStateKey(Address address) => address.ToHex().ToLowerInvariant(); + + internal static string ToFungibleAssetKey(Address address, Currency currency) => + "_" + address.ToHex().ToLowerInvariant() + + "_" + ByteUtil.Hex(currency.Hash.ByteArray).ToLowerInvariant(); + + internal static string ToFungibleAssetKey((Address, Currency) pair) => + ToFungibleAssetKey(pair.Item1, pair.Item2); + } +} From 09a1038d70eefde14ff4f1279f63befba4957263 Mon Sep 17 00:00:00 2001 From: moreal Date: Wed, 14 Oct 2020 03:05:05 +0900 Subject: [PATCH 3/7] feat(mpt): provide API to get state root hash --- Libplanet.Tests/Action/ActionContextTest.cs | 28 +++++++++++++++++++ .../Blockchain/BlockEvaluatorTest.cs | 2 +- Libplanet.Tests/TestUtils.cs | 3 +- Libplanet/Action/ActionContext.cs | 26 +++++++++++++++-- Libplanet/Action/ActionEvaluation.cs | 9 ++++-- .../Action/IAccountStateDeltaExtensions.cs | 14 ++++++++++ Libplanet/Action/IActionContext.cs | 9 ++++++ Libplanet/Blockchain/BlockChain.cs | 12 ++++++-- Libplanet/Blockchain/BlockEvaluator.cs | 15 ++++++++-- Libplanet/Store/TrieStateStore.cs | 8 ++++++ 10 files changed, 116 insertions(+), 10 deletions(-) diff --git a/Libplanet.Tests/Action/ActionContextTest.cs b/Libplanet.Tests/Action/ActionContextTest.cs index 89309f29e64..fbc2ce219a4 100644 --- a/Libplanet.Tests/Action/ActionContextTest.cs +++ b/Libplanet.Tests/Action/ActionContextTest.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Immutable; +using System.Linq; using Bencodex.Types; using Libplanet.Action; using Libplanet.Assets; +using Libplanet.Tests.Store.Trie; using Xunit; namespace Libplanet.Tests.Action @@ -128,6 +130,32 @@ public void GetUnconsumedContext() ); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void LazyPreviousStateRootHash(bool callPreviousStateRootHash) + { + var keyValueStore = new MemoryKeyValueStore(); + var previousBlockStatesTrie = new Libplanet.Store.Trie.MerkleTrie(keyValueStore); + 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
UpdatedAddresses => diff --git a/Libplanet.Tests/Blockchain/BlockEvaluatorTest.cs b/Libplanet.Tests/Blockchain/BlockEvaluatorTest.cs index 3daa30b1737..3ca417fc656 100644 --- a/Libplanet.Tests/Blockchain/BlockEvaluatorTest.cs +++ b/Libplanet.Tests/Blockchain/BlockEvaluatorTest.cs @@ -40,7 +40,7 @@ public void Idempotent() transactions: txs, checkStateRootHash: true); var blockEvaluator = - new BlockEvaluator(null, NullStateGetter, NullBalanceGetter); + new BlockEvaluator(null, NullStateGetter, NullBalanceGetter, null); var generatedRandomNumbers = new List(); Assert.NotEqual(stateRootBlock.Hash, noStateRootBlock.Hash); diff --git a/Libplanet.Tests/TestUtils.cs b/Libplanet.Tests/TestUtils.cs index 8600caf5f43..adb09923d86 100644 --- a/Libplanet.Tests/TestUtils.cs +++ b/Libplanet.Tests/TestUtils.cs @@ -206,7 +206,8 @@ public static byte[] GetRandomBytes(int size) var blockEvaluator = new BlockEvaluator( 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.Reject) .GetTotalDelta(ToStateKey, ToFungibleAssetKey); diff --git a/Libplanet/Action/ActionContext.cs b/Libplanet/Action/ActionContext.cs index 7b3b5a0e44e..77dbbe59c8b 100644 --- a/Libplanet/Action/ActionContext.cs +++ b/Libplanet/Action/ActionContext.cs @@ -1,10 +1,14 @@ +#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; public ActionContext( Address signer, @@ -12,7 +16,8 @@ internal class ActionContext : IActionContext long blockIndex, IAccountStateDelta previousStates, int randomSeed, - bool rehearsal = false + bool rehearsal = false, + ITrie? previousBlockStatesTrie = null ) { Signer = signer; @@ -22,6 +27,7 @@ internal class ActionContext : IActionContext PreviousStates = previousStates; Random = new Random(randomSeed); _randomSeed = randomSeed; + _previousBlockStatesTrie = previousBlockStatesTrie; } public Address Signer { get; } @@ -36,8 +42,24 @@ internal class ActionContext : IActionContext public IRandom Random { get; } + public HashDigest? PreviousStateRootHash + { + get + { + _previousBlockStatesTrie?.Set(PreviousStates.GetUpdatedRawStates()); + return _previousBlockStatesTrie?.Commit().Hash; + } + } + [Pure] public IActionContext GetUnconsumedContext() => - new ActionContext(Signer, Miner, BlockIndex, PreviousStates, _randomSeed, Rehearsal); + new ActionContext( + Signer, + Miner, + BlockIndex, + PreviousStates, + _randomSeed, + Rehearsal, + _previousBlockStatesTrie); } } diff --git a/Libplanet/Action/ActionEvaluation.cs b/Libplanet/Action/ActionEvaluation.cs index b2199e20418..fd60275a305 100644 --- a/Libplanet/Action/ActionEvaluation.cs +++ b/Libplanet/Action/ActionEvaluation.cs @@ -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 @@ -84,6 +85,8 @@ public class ActionEvaluation /// Pass true if it is intended /// to be dry-run (i.e., the returned result will be never used). /// The default value is false. + /// The trie to contain states at previous block. + /// /// Enumerates s for each one in /// . The order is the same to the . /// Note that each object @@ -98,7 +101,8 @@ public class ActionEvaluation Address signer, byte[] signature, IImmutableList actions, - bool rehearsal = false) + bool rehearsal = false, + ITrie previousBlockStatesTrie = null) { ActionContext CreateActionContext( IAccountStateDelta prevStates, @@ -110,7 +114,8 @@ int randomSeed blockIndex: blockIndex, previousStates: prevStates, randomSeed: randomSeed, - rehearsal: rehearsal + rehearsal: rehearsal, + previousBlockStatesTrie: previousBlockStatesTrie ); byte[] hashedSignature; diff --git a/Libplanet/Action/IAccountStateDeltaExtensions.cs b/Libplanet/Action/IAccountStateDeltaExtensions.cs index 9d3e53df879..34b56bffe08 100644 --- a/Libplanet/Action/IAccountStateDeltaExtensions.cs +++ b/Libplanet/Action/IAccountStateDeltaExtensions.cs @@ -4,6 +4,7 @@ using System.Linq; using Bencodex.Types; using Libplanet.Assets; +using static Libplanet.Blockchain.KeyConverters; namespace Libplanet.Action { @@ -31,5 +32,18 @@ internal static class IAccountStateDeltaExtensions ) ) ).ToImmutableDictionary(); + + internal static IImmutableDictionary GetUpdatedRawStates( + this IAccountStateDelta delta) => + delta.GetUpdatedStates() + .Select(pair => + new KeyValuePair( + ToStateKey(pair.Key), + pair.Value)) + .Union( + delta.GetUpdatedBalances().Select(pair => + new KeyValuePair( + ToFungibleAssetKey(pair.Key), + (Integer)pair.Value.RawValue))).ToImmutableDictionary(); } } diff --git a/Libplanet/Action/IActionContext.cs b/Libplanet/Action/IActionContext.cs index f0150924ec8..19f2ebacb9c 100644 --- a/Libplanet/Action/IActionContext.cs +++ b/Libplanet/Action/IActionContext.cs @@ -1,4 +1,6 @@ +#nullable enable using System.Diagnostics.Contracts; +using System.Security.Cryptography; namespace Libplanet.Action { @@ -61,6 +63,13 @@ public interface IActionContext /// to . IRandom Random { get; } + /// + /// A state root hash at the . It can cause file I/O interrupt. + /// It will be return null if the implementation or your chain didn't support + /// the state root hash feature. + /// + HashDigest? PreviousStateRootHash { get; } + /// /// Returns a clone of this context, except that its has the unconsumed /// state (with the same seed). The clone and its original are a distinct instance diff --git a/Libplanet/Blockchain/BlockChain.cs b/Libplanet/Blockchain/BlockChain.cs index 17850f972fd..6984ee9eb13 100644 --- a/Libplanet/Blockchain/BlockChain.cs +++ b/Libplanet/Blockchain/BlockChain.cs @@ -162,7 +162,14 @@ IEnumerable> renderers _logger = Log.ForContext>() .ForContext("CanonicalChainId", Id); - BlockEvaluator = new BlockEvaluator(policy.BlockAction, GetState, GetBalance); + Func, ITrie> trieGetter = StateStore is TrieStateStore trieStateStore + ? h => trieStateStore.GetTrie(h) + : (Func, ITrie>)null; + BlockEvaluator = new BlockEvaluator( + policy.BlockAction, + GetState, + GetBalance, + trieGetter); if (Count == 0) { @@ -373,7 +380,8 @@ public Block Tip blockAction, (address, digest, stateCompleter) => null, (address, currency, hash, fungibleAssetStateCompleter) - => new FungibleAssetValue(currency)); + => new FungibleAssetValue(currency), + null); var actionEvaluationResult = blockEvaluator .EvaluateActions(block, StateCompleterSet.Reject) .GetTotalDelta(ToStateKey, ToFungibleAssetKey); diff --git a/Libplanet/Blockchain/BlockEvaluator.cs b/Libplanet/Blockchain/BlockEvaluator.cs index 83dee72f4ff..1ec015493c6 100644 --- a/Libplanet/Blockchain/BlockEvaluator.cs +++ b/Libplanet/Blockchain/BlockEvaluator.cs @@ -8,6 +8,7 @@ using Libplanet.Action; using Libplanet.Assets; using Libplanet.Blocks; +using Libplanet.Store.Trie; using Serilog; namespace Libplanet.Blockchain @@ -24,16 +25,20 @@ internal class BlockEvaluator FungibleAssetStateCompleter, FungibleAssetValue> _balanceGetter; + private readonly Func, ITrie>? _trieGetter; + internal BlockEvaluator( IAction? blockAction, Func?, StateCompleter, IValue?> stateGetter, Func?, - FungibleAssetStateCompleter, FungibleAssetValue> balanceGetter) + FungibleAssetStateCompleter, FungibleAssetValue> balanceGetter, + Func, ITrie>? trieGetter) { _logger = Log.ForContext(typeof(BlockEvaluator)); _blockAction = blockAction; _stateGetter = stateGetter; _balanceGetter = balanceGetter; + _trieGetter = trieGetter; } internal IReadOnlyList EvaluateActions( @@ -115,6 +120,11 @@ StateCompleterSet stateCompleters ); } + ITrie? previousBlockStatesTrie = + !(_trieGetter is null) && block.PreviousHash is HashDigest h + ? _trieGetter(h) + : null; + return ActionEvaluation.EvaluateActionsGradually( block.PreEvaluationHash, block.Index, @@ -123,7 +133,8 @@ StateCompleterSet stateCompleters miner, miner, Array.Empty(), - new[] { _blockAction }.ToImmutableList()).First(); + new[] { _blockAction }.ToImmutableList(), + previousBlockStatesTrie: previousBlockStatesTrie).First(); } } } diff --git a/Libplanet/Store/TrieStateStore.cs b/Libplanet/Store/TrieStateStore.cs index b9f29688f8f..a47eea7dd46 100644 --- a/Libplanet/Store/TrieStateStore.cs +++ b/Libplanet/Store/TrieStateStore.cs @@ -190,5 +190,13 @@ public void Dispose() /// . public HashDigest GetRootHash(HashDigest blockHash) => new HashDigest(_stateHashKeyValueStore.Get(blockHash.ToByteArray())); + + internal ITrie GetTrie(HashDigest blockHash) + => + new MerkleTrie( + _stateKeyValueStore, + new HashNode( + new HashDigest( + _stateHashKeyValueStore.Get(blockHash.ToByteArray())))); } } From ba6ee8ca397c034288024c84e04b78ec11907807 Mon Sep 17 00:00:00 2001 From: moreal Date: Wed, 14 Oct 2020 15:32:44 +0900 Subject: [PATCH 4/7] feat(mpt): make 'set' operation immutable --- Libplanet.Tests/Action/ActionContextTest.cs | 5 +++-- .../Store/Trie/MerkleTrieExtensionsTest.cs | 16 ++++++++-------- Libplanet.Tests/Store/Trie/MerkleTrieTest.cs | 4 ++-- Libplanet.Tests/Store/Trie/TrieTest.cs | 12 ++++++------ Libplanet.Tests/TestUtils.cs | 4 ++-- Libplanet/Action/ActionContext.cs | 13 +++++-------- Libplanet/Blockchain/BlockChain.cs | 4 ++-- Libplanet/Store/Trie/ITrie.cs | 3 ++- Libplanet/Store/Trie/ITrieExtensions.cs | 10 ++++++---- Libplanet/Store/Trie/MerkleTrie.cs | 8 +++++--- Libplanet/Store/TrieStateStore.cs | 4 ++-- 11 files changed, 43 insertions(+), 40 deletions(-) diff --git a/Libplanet.Tests/Action/ActionContextTest.cs b/Libplanet.Tests/Action/ActionContextTest.cs index fbc2ce219a4..da472e225f4 100644 --- a/Libplanet.Tests/Action/ActionContextTest.cs +++ b/Libplanet.Tests/Action/ActionContextTest.cs @@ -4,6 +4,7 @@ using Bencodex.Types; using Libplanet.Action; using Libplanet.Assets; +using Libplanet.Store.Trie; using Libplanet.Tests.Store.Trie; using Xunit; @@ -136,8 +137,8 @@ public void GetUnconsumedContext() public void LazyPreviousStateRootHash(bool callPreviousStateRootHash) { var keyValueStore = new MemoryKeyValueStore(); - var previousBlockStatesTrie = new Libplanet.Store.Trie.MerkleTrie(keyValueStore); - previousBlockStatesTrie.Set(new byte[0], default(Null)); + 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(), diff --git a/Libplanet.Tests/Store/Trie/MerkleTrieExtensionsTest.cs b/Libplanet.Tests/Store/Trie/MerkleTrieExtensionsTest.cs index 25ee8aa51c4..7e73f94a82c 100644 --- a/Libplanet.Tests/Store/Trie/MerkleTrieExtensionsTest.cs +++ b/Libplanet.Tests/Store/Trie/MerkleTrieExtensionsTest.cs @@ -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 Root, IValue Value)[]> differentNodes = trieA.DifferentNodes(trieB).ToDictionary( diff --git a/Libplanet.Tests/Store/Trie/MerkleTrieTest.cs b/Libplanet.Tests/Store/Trie/MerkleTrieTest.cs index 88e10552b71..8b78cdc4d83 100644 --- a/Libplanet.Tests/Store/Trie/MerkleTrieTest.cs +++ b/Libplanet.Tests/Store/Trie/MerkleTrieTest.cs @@ -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()); } diff --git a/Libplanet.Tests/Store/Trie/TrieTest.cs b/Libplanet.Tests/Store/Trie/TrieTest.cs index ae92bf533e9..a665ec0c785 100644 --- a/Libplanet.Tests/Store/Trie/TrieTest.cs +++ b/Libplanet.Tests/Store/Trie/TrieTest.cs @@ -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(); } } @@ -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 rootHash = trieA.Hash; Assert.True(trieA.TryGet(path, out IValue stateA)); Assert.Equal((Text)"foo", stateA); @@ -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); @@ -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); } @@ -124,7 +124,7 @@ public void ThrowArgumentNullExceptionWhenSettingNull() Assert.Throws(() => { - trie.Set(new byte[] { 0xbe, 0xef }, null); + _ = trie.Set(new byte[] { 0xbe, 0xef }, null); }); } } diff --git a/Libplanet.Tests/TestUtils.cs b/Libplanet.Tests/TestUtils.cs index adb09923d86..7411cc6c0ff 100644 --- a/Libplanet.Tests/TestUtils.cs +++ b/Libplanet.Tests/TestUtils.cs @@ -211,10 +211,10 @@ public static byte[] GetRandomBytes(int size) var actionEvaluationResult = blockEvaluator .EvaluateActions(block, StateCompleterSet.Reject) .GetTotalDelta(ToStateKey, ToFungibleAssetKey); - var trie = new MerkleTrie(new DefaultKeyValueStore(null)); + 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; diff --git a/Libplanet/Action/ActionContext.cs b/Libplanet/Action/ActionContext.cs index 77dbbe59c8b..30b6a4480f8 100644 --- a/Libplanet/Action/ActionContext.cs +++ b/Libplanet/Action/ActionContext.cs @@ -42,14 +42,11 @@ internal class ActionContext : IActionContext public IRandom Random { get; } - public HashDigest? PreviousStateRootHash - { - get - { - _previousBlockStatesTrie?.Set(PreviousStates.GetUpdatedRawStates()); - return _previousBlockStatesTrie?.Commit().Hash; - } - } + public HashDigest? PreviousStateRootHash => + _previousBlockStatesTrie? + .Set(PreviousStates.GetUpdatedRawStates()) + .Commit() + .Hash; [Pure] public IActionContext GetUnconsumedContext() => diff --git a/Libplanet/Blockchain/BlockChain.cs b/Libplanet/Blockchain/BlockChain.cs index 6984ee9eb13..a0573a20e40 100644 --- a/Libplanet/Blockchain/BlockChain.cs +++ b/Libplanet/Blockchain/BlockChain.cs @@ -385,8 +385,8 @@ public Block Tip var actionEvaluationResult = blockEvaluator .EvaluateActions(block, StateCompleterSet.Reject) .GetTotalDelta(ToStateKey, ToFungibleAssetKey); - var trie = new MerkleTrie(new DefaultKeyValueStore(null)); - trie.Set(actionEvaluationResult); + ITrie trie = new MerkleTrie(new DefaultKeyValueStore(null)); + trie = trie.Set(actionEvaluationResult); var stateRootHash = trie.Commit(rehearsal: true).Hash; return new Block( diff --git a/Libplanet/Store/Trie/ITrie.cs b/Libplanet/Store/Trie/ITrie.cs index 8f5c0352ffa..4ff0e484ca4 100644 --- a/Libplanet/Store/Trie/ITrie.cs +++ b/Libplanet/Store/Trie/ITrie.cs @@ -23,7 +23,8 @@ public interface ITrie /// The value to store. /// Thrown when the given /// is null. - void Set(byte[] key, IValue value); + /// Returns new updated . + ITrie Set(byte[] key, IValue value); /// /// Gets the value stored with in . diff --git a/Libplanet/Store/Trie/ITrieExtensions.cs b/Libplanet/Store/Trie/ITrieExtensions.cs index 7939dbb59ee..e388140bb64 100644 --- a/Libplanet/Store/Trie/ITrieExtensions.cs +++ b/Libplanet/Store/Trie/ITrieExtensions.cs @@ -6,17 +6,19 @@ namespace Libplanet.Store.Trie { internal static class ITrieExtensions { - public static void Set(this ITrie trie, IImmutableDictionary values) + public static ITrie Set(this ITrie trie, IImmutableDictionary values) { foreach (var pair in values) { - trie.Set(pair.Key, pair.Value); + trie = trie.Set(pair.Key, pair.Value); } + + return trie; } - public static void Set(this ITrie trie, IImmutableDictionary values) + public static ITrie Set(this ITrie trie, IImmutableDictionary values) { - trie.Set(values.ToImmutableDictionary( + return trie.Set(values.ToImmutableDictionary( pair => Encoding.UTF8.GetBytes(pair.Key), pair => pair.Value)); } diff --git a/Libplanet/Store/Trie/MerkleTrie.cs b/Libplanet/Store/Trie/MerkleTrie.cs index ccca2579354..19cf5012aed 100644 --- a/Libplanet/Store/Trie/MerkleTrie.cs +++ b/Libplanet/Store/Trie/MerkleTrie.cs @@ -69,7 +69,7 @@ internal MerkleTrie(IKeyValueStore keyValueStore, INode? root = null, bool secur public HashDigest Hash => Root?.Hash() ?? EmptyRootHash; - private INode? Root { get; set; } + private INode? Root { get; } private IKeyValueStore KeyValueStore { get; } @@ -80,18 +80,20 @@ internal MerkleTrie(IKeyValueStore keyValueStore, INode? root = null, bool secur Operator.Weave(left, right); /// - public void Set(byte[] key, IValue value) + public ITrie Set(byte[] key, IValue value) { if (value is null) { throw new ArgumentNullException(nameof(value)); } - Root = Insert( + INode newRootNode = Insert( Root, ImmutableArray.Empty, ToKey(key).ToImmutableArray(), new ValueNode(value)); + + return new MerkleTrie(KeyValueStore, newRootNode, _secure); } /// diff --git a/Libplanet/Store/TrieStateStore.cs b/Libplanet/Store/TrieStateStore.cs index a47eea7dd46..1ab30aeeb5c 100644 --- a/Libplanet/Store/TrieStateStore.cs +++ b/Libplanet/Store/TrieStateStore.cs @@ -48,7 +48,7 @@ public class TrieStateStore : IStateStore, IDisposable IImmutableDictionary states) where T : IAction, new() { - MerkleTrie prevStatesTrie; + ITrie prevStatesTrie; var previousBlockStateHashBytes = block.PreviousHash is null ? null : _stateHashKeyValueStore.Get(block.PreviousHash.Value.ToByteArray()); @@ -59,7 +59,7 @@ public class TrieStateStore : IStateStore, IDisposable foreach (var pair in states) { - prevStatesTrie.Set(Encoding.UTF8.GetBytes(pair.Key), pair.Value); + prevStatesTrie = prevStatesTrie.Set(Encoding.UTF8.GetBytes(pair.Key), pair.Value); } var newStateTrie = prevStatesTrie.Commit(); From 19a22c6be209bc60ab78a2efcc124ae1501554ce Mon Sep 17 00:00:00 2001 From: moreal Date: Wed, 14 Oct 2020 15:37:53 +0900 Subject: [PATCH 5/7] docs: update changelog --- CHANGES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index d528832316c..a214127feb2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -226,6 +226,8 @@ To be released. - Added `Block.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 @@ -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 From fcfada1910ad7a8782cd572108462e9fbd5f3e09 Mon Sep 17 00:00:00 2001 From: moreal Date: Thu, 15 Oct 2020 10:40:16 +0900 Subject: [PATCH 6/7] refactor(mpt): cache calculation result of `PreviousStateRootHash` --- Libplanet/Action/ActionContext.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Libplanet/Action/ActionContext.cs b/Libplanet/Action/ActionContext.cs index 30b6a4480f8..a2b8cb5e60a 100644 --- a/Libplanet/Action/ActionContext.cs +++ b/Libplanet/Action/ActionContext.cs @@ -10,6 +10,8 @@ internal class ActionContext : IActionContext private readonly int _randomSeed; private readonly ITrie? _previousBlockStatesTrie; + private HashDigest? _previousStateRootHash; + public ActionContext( Address signer, Address miner, @@ -42,11 +44,16 @@ internal class ActionContext : IActionContext public IRandom Random { get; } - public HashDigest? PreviousStateRootHash => - _previousBlockStatesTrie? - .Set(PreviousStates.GetUpdatedRawStates()) - .Commit() - .Hash; + public HashDigest? PreviousStateRootHash + { + get + { + return _previousStateRootHash ??= _previousBlockStatesTrie? + .Set(PreviousStates.GetUpdatedRawStates()) + .Commit() + .Hash; + } + } [Pure] public IActionContext GetUnconsumedContext() => From 64428362cc7ce943129aec8967d6a1fc2ca321e4 Mon Sep 17 00:00:00 2001 From: moreal Date: Thu, 15 Oct 2020 10:52:48 +0900 Subject: [PATCH 7/7] test: fix test Make the trie usage immutable. --- Libplanet.Tests/Store/Trie/MerkleTrieExtensionsTest.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Libplanet.Tests/Store/Trie/MerkleTrieExtensionsTest.cs b/Libplanet.Tests/Store/Trie/MerkleTrieExtensionsTest.cs index 7e73f94a82c..2eaa1bed57d 100644 --- a/Libplanet.Tests/Store/Trie/MerkleTrieExtensionsTest.cs +++ b/Libplanet.Tests/Store/Trie/MerkleTrieExtensionsTest.cs @@ -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, IValue> states = trie.ListAllStates().ToDictionary(